test_discover_saved_queries.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. from __future__ import absolute_import
  2. from sentry.testutils import APITestCase, SnubaTestCase
  3. from django.core.urlresolvers import reverse
  4. from sentry.discover.models import DiscoverSavedQuery
  5. from sentry.testutils.helpers.datetime import before_now
  6. class DiscoverSavedQueryBase(APITestCase, SnubaTestCase):
  7. def setUp(self):
  8. super(DiscoverSavedQueryBase, self).setUp()
  9. self.login_as(user=self.user)
  10. self.org = self.create_organization(owner=self.user)
  11. self.projects = [
  12. self.create_project(organization=self.org),
  13. self.create_project(organization=self.org),
  14. ]
  15. self.project_ids = [project.id for project in self.projects]
  16. self.project_ids_without_access = [self.create_project().id]
  17. query = {"fields": ["test"], "conditions": [], "limit": 10}
  18. model = DiscoverSavedQuery.objects.create(
  19. organization=self.org, created_by=self.user, name="Test query", query=query, version=1
  20. )
  21. model.set_projects(self.project_ids)
  22. class DiscoverSavedQueriesTest(DiscoverSavedQueryBase):
  23. feature_name = "organizations:discover"
  24. def setUp(self):
  25. super(DiscoverSavedQueriesTest, self).setUp()
  26. self.url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  27. def test_get(self):
  28. with self.feature(self.feature_name):
  29. response = self.client.get(self.url)
  30. assert response.status_code == 200, response.content
  31. assert len(response.data) == 1
  32. assert response.data[0]["name"] == "Test query"
  33. assert response.data[0]["projects"] == self.project_ids
  34. assert response.data[0]["fields"] == ["test"]
  35. assert response.data[0]["conditions"] == []
  36. assert response.data[0]["limit"] == 10
  37. assert response.data[0]["version"] == 1
  38. assert "createdBy" in response.data[0]
  39. assert response.data[0]["createdBy"]["username"] == self.user.username
  40. def test_get_version_filter(self):
  41. with self.feature(self.feature_name):
  42. response = self.client.get(self.url, format="json", data={"query": "version:1"})
  43. assert response.status_code == 200, response.content
  44. assert len(response.data) == 1
  45. assert response.data[0]["name"] == "Test query"
  46. with self.feature(self.feature_name):
  47. response = self.client.get(self.url, format="json", data={"query": "version:2"})
  48. assert response.status_code == 200, response.content
  49. assert len(response.data) == 0
  50. def test_get_name_filter(self):
  51. with self.feature(self.feature_name):
  52. response = self.client.get(self.url, format="json", data={"query": "Test"})
  53. assert response.status_code == 200, response.content
  54. assert len(response.data) == 1
  55. assert response.data[0]["name"] == "Test query"
  56. with self.feature(self.feature_name):
  57. # Also available as the name: filter.
  58. response = self.client.get(self.url, format="json", data={"query": "name:Test"})
  59. assert response.status_code == 200, response.content
  60. assert len(response.data) == 1
  61. assert response.data[0]["name"] == "Test query"
  62. with self.feature(self.feature_name):
  63. response = self.client.get(self.url, format="json", data={"query": "name:Nope"})
  64. assert response.status_code == 200, response.content
  65. assert len(response.data) == 0
  66. def test_get_all_paginated(self):
  67. for i in range(0, 10):
  68. query = {"fields": ["test"], "conditions": [], "limit": 10}
  69. model = DiscoverSavedQuery.objects.create(
  70. organization=self.org,
  71. created_by=self.user,
  72. name="My query {}".format(i),
  73. query=query,
  74. version=1,
  75. )
  76. model.set_projects(self.project_ids)
  77. with self.feature(self.feature_name):
  78. response = self.client.get(self.url, data={"per_page": 1})
  79. assert response.status_code == 200, response.content
  80. assert len(response.data) == 1
  81. with self.feature(self.feature_name):
  82. # The all parameter ignores pagination and returns all values.
  83. response = self.client.get(self.url, data={"per_page": 1, "all": 1})
  84. assert response.status_code == 200, response.content
  85. assert len(response.data) == 11
  86. def test_get_sortby(self):
  87. query = {"fields": ["message"], "query": "", "limit": 10}
  88. model = DiscoverSavedQuery.objects.create(
  89. organization=self.org,
  90. created_by=self.user,
  91. name="My query",
  92. query=query,
  93. version=2,
  94. date_created=before_now(minutes=10),
  95. date_updated=before_now(minutes=10),
  96. )
  97. model.set_projects(self.project_ids)
  98. sort_options = {
  99. "dateCreated": True,
  100. "-dateCreated": False,
  101. "dateUpdated": True,
  102. "-dateUpdated": False,
  103. "name": True,
  104. "-name": False,
  105. }
  106. for sorting, forward_sort in sort_options.items():
  107. with self.feature(self.feature_name):
  108. response = self.client.get(self.url, data={"sortBy": sorting})
  109. assert response.status_code == 200
  110. values = [row[sorting.strip("-")] for row in response.data]
  111. if not forward_sort:
  112. values = list(reversed(values))
  113. assert list(sorted(values)) == values
  114. def test_get_sortby_myqueries(self):
  115. uhoh_user = self.create_user(username="uhoh")
  116. self.create_member(organization=self.org, user=uhoh_user)
  117. whoops_user = self.create_user(username="whoops")
  118. self.create_member(organization=self.org, user=whoops_user)
  119. query = {"fields": ["message"], "query": "", "limit": 10}
  120. model = DiscoverSavedQuery.objects.create(
  121. organization=self.org,
  122. created_by=uhoh_user,
  123. name="a query for uhoh",
  124. query=query,
  125. version=2,
  126. date_created=before_now(minutes=10),
  127. date_updated=before_now(minutes=10),
  128. )
  129. model.set_projects(self.project_ids)
  130. model = DiscoverSavedQuery.objects.create(
  131. organization=self.org,
  132. created_by=whoops_user,
  133. name="a query for whoops",
  134. query=query,
  135. version=2,
  136. date_created=before_now(minutes=10),
  137. date_updated=before_now(minutes=10),
  138. )
  139. model.set_projects(self.project_ids)
  140. with self.feature(self.feature_name):
  141. response = self.client.get(self.url, data={"sortBy": "myqueries"})
  142. assert response.status_code == 200, response.content
  143. values = [int(item["createdBy"]["id"]) for item in response.data]
  144. assert values == [self.user.id, uhoh_user.id, whoops_user.id]
  145. def test_post(self):
  146. with self.feature(self.feature_name):
  147. response = self.client.post(
  148. self.url,
  149. {
  150. "name": "New query",
  151. "projects": self.project_ids,
  152. "fields": [],
  153. "range": "24h",
  154. "limit": 20,
  155. "conditions": [],
  156. "aggregations": [],
  157. "orderby": "-time",
  158. },
  159. )
  160. assert response.status_code == 201, response.content
  161. assert response.data["name"] == "New query"
  162. assert response.data["projects"] == self.project_ids
  163. assert response.data["range"] == "24h"
  164. assert not hasattr(response.data, "start")
  165. assert not hasattr(response.data, "end")
  166. def test_post_invalid_projects(self):
  167. with self.feature(self.feature_name):
  168. response = self.client.post(
  169. self.url,
  170. {
  171. "name": "New query",
  172. "projects": self.project_ids_without_access,
  173. "fields": [],
  174. "range": "24h",
  175. "limit": 20,
  176. "conditions": [],
  177. "aggregations": [],
  178. "orderby": "-time",
  179. },
  180. )
  181. assert response.status_code == 403, response.content
  182. def test_post_all_projects(self):
  183. with self.feature(self.feature_name):
  184. response = self.client.post(
  185. self.url,
  186. {
  187. "name": "All projects",
  188. "projects": [-1],
  189. "conditions": [],
  190. "fields": ["title", "count()"],
  191. "range": "24h",
  192. "orderby": "time",
  193. },
  194. )
  195. assert response.status_code == 201, response.content
  196. assert response.data["projects"] == [-1]
  197. assert response.data["name"] == "All projects"
  198. def test_post_cannot_use_version_two_fields(self):
  199. with self.feature(self.feature_name):
  200. response = self.client.post(
  201. self.url,
  202. {
  203. "name": "New query",
  204. "projects": self.project_ids,
  205. "fields": ["id"],
  206. "range": "24h",
  207. "limit": 20,
  208. "environment": ["dev"],
  209. "yAxis": "count(id)",
  210. "aggregations": [],
  211. "orderby": "-time",
  212. },
  213. )
  214. assert response.status_code == 400, response.content
  215. assert "cannot use the environment, yAxis attribute(s)" in response.content
  216. class DiscoverSavedQueriesVersion2Test(DiscoverSavedQueryBase):
  217. feature_name = "organizations:discover-query"
  218. def setUp(self):
  219. super(DiscoverSavedQueriesVersion2Test, self).setUp()
  220. self.url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  221. def test_post_invalid_conditions(self):
  222. with self.feature(self.feature_name):
  223. response = self.client.post(
  224. self.url,
  225. {
  226. "name": "New query",
  227. "projects": self.project_ids,
  228. "fields": ["title", "count()"],
  229. "range": "24h",
  230. "version": 2,
  231. "conditions": [["field", "=", "value"]],
  232. },
  233. )
  234. assert response.status_code == 400, response.content
  235. assert "cannot use the conditions attribute(s)" in response.content
  236. def test_post_require_selected_fields(self):
  237. with self.feature(self.feature_name):
  238. response = self.client.post(
  239. self.url,
  240. {
  241. "name": "New query",
  242. "projects": self.project_ids,
  243. "fields": [],
  244. "range": "24h",
  245. "version": 2,
  246. },
  247. )
  248. assert response.status_code == 400, response.content
  249. assert "include at least one field" in response.content
  250. def test_post_success(self):
  251. with self.feature(self.feature_name):
  252. response = self.client.post(
  253. self.url,
  254. {
  255. "name": "new query",
  256. "projects": self.project_ids,
  257. "fields": ["title", "count()", "project"],
  258. "environment": ["dev"],
  259. "query": "event.type:error browser.name:Firefox",
  260. "range": "24h",
  261. "yAxis": "count(id)",
  262. "display": "releases",
  263. "version": 2,
  264. },
  265. )
  266. assert response.status_code == 201, response.content
  267. data = response.data
  268. assert data["fields"] == ["title", "count()", "project"]
  269. assert data["range"] == "24h"
  270. assert data["environment"] == ["dev"]
  271. assert data["query"] == "event.type:error browser.name:Firefox"
  272. assert data["yAxis"] == "count(id)"
  273. assert data["display"] == "releases"
  274. assert data["version"] == 2
  275. def test_post_all_projects(self):
  276. with self.feature(self.feature_name):
  277. response = self.client.post(
  278. self.url,
  279. {
  280. "name": "New query",
  281. "projects": [-1],
  282. "fields": ["title", "count()"],
  283. "range": "24h",
  284. "version": 2,
  285. },
  286. )
  287. assert response.status_code == 201, response.content
  288. assert response.data["projects"] == [-1]
  289. def test_save_with_project(self):
  290. with self.feature(self.feature_name):
  291. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  292. response = self.client.post(
  293. url,
  294. {
  295. "name": "project query",
  296. "projects": self.project_ids,
  297. "fields": ["title", "count()"],
  298. "range": "24h",
  299. "query": "project:{}".format(self.projects[0].slug),
  300. "version": 2,
  301. },
  302. )
  303. assert response.status_code == 201, response.content
  304. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  305. def test_save_with_project_and_my_projects(self):
  306. team = self.create_team(organization=self.org, members=[self.user])
  307. project = self.create_project(organization=self.org, teams=[team])
  308. with self.feature(self.feature_name):
  309. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  310. response = self.client.post(
  311. url,
  312. {
  313. "name": "project query",
  314. "projects": [],
  315. "fields": ["title", "count()"],
  316. "range": "24h",
  317. "query": "project:{}".format(project.slug),
  318. "version": 2,
  319. },
  320. )
  321. assert response.status_code == 201, response.content
  322. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  323. def test_save_with_org_projects(self):
  324. project = self.create_project(organization=self.org)
  325. with self.feature(self.feature_name):
  326. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  327. response = self.client.post(
  328. url,
  329. {
  330. "name": "project query",
  331. "projects": [project.id],
  332. "fields": ["title", "count()"],
  333. "range": "24h",
  334. "version": 2,
  335. },
  336. )
  337. assert response.status_code == 201, response.content
  338. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  339. def test_save_with_team_project(self):
  340. team = self.create_team(organization=self.org, members=[self.user])
  341. project = self.create_project(organization=self.org, teams=[team])
  342. self.create_project(organization=self.org, teams=[team])
  343. with self.feature(self.feature_name):
  344. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  345. response = self.client.post(
  346. url,
  347. {
  348. "name": "project query",
  349. "projects": [project.id],
  350. "fields": ["title", "count()"],
  351. "range": "24h",
  352. "version": 2,
  353. },
  354. )
  355. assert response.status_code == 201, response.content
  356. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  357. def test_save_with_wrong_projects(self):
  358. other_org = self.create_organization(owner=self.user)
  359. project = self.create_project(organization=other_org)
  360. project2 = self.create_project(organization=self.org)
  361. with self.feature(self.feature_name):
  362. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  363. response = self.client.post(
  364. url,
  365. {
  366. "name": "project query",
  367. "projects": [project.id],
  368. "fields": ["title", "count()"],
  369. "range": "24h",
  370. "query": "project:{}".format(project.slug),
  371. "version": 2,
  372. },
  373. )
  374. assert response.status_code == 403, response.content
  375. assert not DiscoverSavedQuery.objects.filter(name="project query").exists()
  376. with self.feature(self.feature_name):
  377. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  378. response = self.client.post(
  379. url,
  380. {
  381. "name": "project query",
  382. "projects": [project.id, project2.id],
  383. "fields": ["title", "count()"],
  384. "range": "24h",
  385. "query": "project:{} project:{}".format(project.slug, project2.slug),
  386. "version": 2,
  387. },
  388. )
  389. assert response.status_code == 403, response.content
  390. assert not DiscoverSavedQuery.objects.filter(name="project query").exists()
  391. # Mix of wrong + valid
  392. with self.feature(self.feature_name):
  393. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  394. response = self.client.post(
  395. url,
  396. {
  397. "name": "project query",
  398. "projects": [-1],
  399. "fields": ["title", "count()"],
  400. "range": "24h",
  401. "query": "project:{} project:{}".format(project.slug, project2.slug),
  402. "version": 2,
  403. },
  404. )
  405. assert response.status_code == 400, response.content
  406. assert not DiscoverSavedQuery.objects.filter(name="project query").exists()
  407. def test_save_invalid_query(self):
  408. with self.feature(self.feature_name):
  409. response = self.client.post(
  410. self.url,
  411. {
  412. "name": "Bad query",
  413. "projects": [-1],
  414. "fields": ["title", "count()"],
  415. "range": "24h",
  416. "query": "spaceAfterColon: 1",
  417. "version": 2,
  418. },
  419. )
  420. assert response.status_code == 400, response.content
  421. assert not DiscoverSavedQuery.objects.filter(name="Bad query").exists()