test_discover_saved_query_detail.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. import pytest
  2. from django.urls import NoReverseMatch, reverse
  3. from sentry.discover.models import (
  4. DiscoverSavedQuery,
  5. DiscoverSavedQueryProject,
  6. DiscoverSavedQueryTypes,
  7. )
  8. from sentry.testutils.cases import APITestCase, SnubaTestCase
  9. class DiscoverSavedQueryDetailTest(APITestCase, SnubaTestCase):
  10. feature_name = "organizations:discover"
  11. def setUp(self):
  12. super().setUp()
  13. self.login_as(user=self.user)
  14. self.org = self.create_organization(owner=self.user)
  15. self.org_without_access = self.create_organization()
  16. self.project_ids = [
  17. self.create_project(organization=self.org).id,
  18. self.create_project(organization=self.org).id,
  19. ]
  20. query = {"fields": ["test"], "conditions": [], "limit": 10}
  21. model = DiscoverSavedQuery.objects.create(
  22. organization=self.org, created_by_id=self.user.id, name="Test query", query=query
  23. )
  24. model.set_projects(self.project_ids)
  25. self.query_id = model.id
  26. invalid = DiscoverSavedQuery.objects.create(
  27. organization=self.org_without_access, name="Query without access", query=query
  28. )
  29. invalid.set_projects(self.project_ids)
  30. self.query_id_without_access = invalid.id
  31. def setup_no_team_user(self):
  32. # disable Open Membership
  33. self.org.flags.allow_joinleave = False
  34. self.org.save()
  35. # user has no access to the first project
  36. user_no_team = self.create_user(is_superuser=False)
  37. self.create_member(user=user_no_team, organization=self.org, role="member", teams=[])
  38. self.login_as(user_no_team)
  39. def test_invalid_id(self):
  40. with pytest.raises(NoReverseMatch):
  41. reverse("sentry-api-0-discover-saved-query-detail", args=[self.org.slug, "not-an-id"])
  42. def test_get(self):
  43. with self.feature(self.feature_name):
  44. url = reverse(
  45. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  46. )
  47. response = self.client.get(url)
  48. assert response.status_code == 200, response.content
  49. assert response.data["id"] == str(self.query_id)
  50. assert set(response.data["projects"]) == set(self.project_ids)
  51. assert response.data["fields"] == ["test"]
  52. assert response.data["conditions"] == []
  53. assert response.data["limit"] == 10
  54. def test_get_discover_query_flag(self):
  55. with self.feature("organizations:discover-query"):
  56. url = reverse(
  57. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  58. )
  59. response = self.client.get(url)
  60. assert response.status_code == 200, response.content
  61. assert response.data["id"] == str(self.query_id)
  62. assert set(response.data["projects"]) == set(self.project_ids)
  63. assert response.data["fields"] == ["test"]
  64. assert response.data["conditions"] == []
  65. assert response.data["limit"] == 10
  66. def test_get_version(self):
  67. query = {"fields": ["event_id"], "query": "event.type:error", "limit": 10, "version": 2}
  68. model = DiscoverSavedQuery.objects.create(
  69. organization=self.org, created_by_id=self.user.id, name="v2 query", query=query
  70. )
  71. model.set_projects(self.project_ids)
  72. with self.feature(self.feature_name):
  73. url = reverse(
  74. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, model.id]
  75. )
  76. response = self.client.get(url)
  77. assert response.status_code == 200, response.content
  78. assert response.data["id"] == str(model.id)
  79. assert set(response.data["projects"]) == set(self.project_ids)
  80. assert response.data["fields"] == ["event_id"]
  81. assert response.data["query"] == "event.type:error"
  82. assert response.data["limit"] == 10
  83. assert response.data["version"] == 2
  84. def test_get_org_without_access(self):
  85. with self.feature(self.feature_name):
  86. url = reverse(
  87. "sentry-api-0-discover-saved-query-detail",
  88. args=[self.org_without_access.slug, self.query_id],
  89. )
  90. response = self.client.get(url)
  91. assert response.status_code == 403, response.content
  92. def test_get_homepage_query(self):
  93. query = {"fields": ["event_id"], "query": "event.type:error", "limit": 10, "version": 2}
  94. model = DiscoverSavedQuery.objects.create(
  95. organization=self.org,
  96. created_by_id=self.user.id,
  97. name="v2 query",
  98. query=query,
  99. is_homepage=True,
  100. )
  101. model.set_projects(self.project_ids)
  102. with self.feature(self.feature_name):
  103. url = reverse(
  104. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, model.id]
  105. )
  106. response = self.client.get(url)
  107. assert response.status_code == 404, response.content
  108. def test_get_disallow_when_no_project_access(self):
  109. self.setup_no_team_user()
  110. with self.feature(self.feature_name):
  111. url = reverse(
  112. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  113. )
  114. response = self.client.get(url)
  115. assert response.status_code == 403, response.data
  116. assert response.data == {"detail": "You do not have permission to perform this action."}
  117. def test_put(self):
  118. with self.feature(self.feature_name):
  119. url = reverse(
  120. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  121. )
  122. response = self.client.put(
  123. url,
  124. {
  125. "name": "New query",
  126. "projects": self.project_ids,
  127. "fields": [],
  128. "range": "24h",
  129. "limit": 20,
  130. "conditions": [],
  131. "aggregations": [],
  132. "orderby": "-time",
  133. },
  134. )
  135. assert response.status_code == 200, response.content
  136. assert response.data["id"] == str(self.query_id)
  137. assert set(response.data["projects"]) == set(self.project_ids)
  138. assert response.data["fields"] == []
  139. assert response.data["conditions"] == []
  140. assert response.data["limit"] == 20
  141. def test_put_dataset(self):
  142. with self.feature(self.feature_name):
  143. url = reverse(
  144. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  145. )
  146. response = self.client.put(
  147. url,
  148. {
  149. "name": "New query",
  150. "projects": self.project_ids,
  151. "fields": [],
  152. "range": "24h",
  153. "limit": 20,
  154. "conditions": [],
  155. "aggregations": [],
  156. "orderby": "-time",
  157. "queryDataset": "transaction-like",
  158. },
  159. )
  160. assert response.status_code == 200, response.content
  161. assert response.data["id"] == str(self.query_id)
  162. assert set(response.data["projects"]) == set(self.project_ids)
  163. assert response.data["fields"] == []
  164. assert response.data["conditions"] == []
  165. assert response.data["limit"] == 20
  166. assert response.data["queryDataset"] == "transaction-like"
  167. def test_dataset_set_to_discover_on_update(self):
  168. query = {"fields": ["event_id"], "query": "event.type:error", "limit": 10, "version": 2}
  169. model = DiscoverSavedQuery.objects.create(
  170. organization=self.org,
  171. created_by_id=self.user.id,
  172. name="query",
  173. query=query,
  174. dataset=DiscoverSavedQueryTypes.TRANSACTION_LIKE,
  175. )
  176. with self.feature(self.feature_name):
  177. url = reverse(
  178. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, model.id]
  179. )
  180. response = self.client.put(
  181. url,
  182. {
  183. "name": "New query",
  184. "projects": self.project_ids,
  185. "fields": [],
  186. "range": "24h",
  187. "limit": 20,
  188. "conditions": [],
  189. "aggregations": [],
  190. "orderby": "-time",
  191. },
  192. )
  193. assert response.status_code == 200, response.content
  194. assert response.data["id"] == str(model.id)
  195. assert response.data["queryDataset"] == "discover"
  196. assert response.data["datasetSource"] == "unknown"
  197. def test_put_with_interval(self):
  198. with self.feature(self.feature_name):
  199. url = reverse(
  200. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  201. )
  202. response = self.client.put(
  203. url,
  204. {
  205. "name": "New query",
  206. "projects": self.project_ids,
  207. "fields": ["transaction", "count()"],
  208. "range": "24h",
  209. "interval": "10m",
  210. "version": 2,
  211. "orderby": "-count",
  212. },
  213. )
  214. assert response.status_code == 200, response.content
  215. assert response.data["fields"] == ["transaction", "count()"]
  216. assert response.data["interval"] == "10m"
  217. def test_put_query_without_access(self):
  218. with self.feature(self.feature_name):
  219. url = reverse(
  220. "sentry-api-0-discover-saved-query-detail",
  221. args=[self.org.slug, self.query_id_without_access],
  222. )
  223. response = self.client.put(
  224. url, {"name": "New query", "projects": self.project_ids, "range": "24h"}
  225. )
  226. assert response.status_code == 404
  227. def test_put_query_with_team(self):
  228. team = self.create_team(organization=self.org, members=[self.user])
  229. project = self.create_project(organization=self.org, teams=[team])
  230. query = DiscoverSavedQuery.objects.create(
  231. organization=self.org,
  232. created_by_id=self.user.id,
  233. name="Test query",
  234. query={"fields": ["test"], "conditions": [], "limit": 10},
  235. )
  236. query.set_projects([project.id])
  237. with self.feature(self.feature_name):
  238. url = reverse(
  239. "sentry-api-0-discover-saved-query-detail",
  240. args=[self.org.slug, query.id],
  241. )
  242. response = self.client.put(url, {"name": "New query", "projects": [], "range": "24h"})
  243. assert response.status_code == 200
  244. def test_put_query_without_team(self):
  245. team = self.create_team(organization=self.org, members=[])
  246. project = self.create_project(organization=self.org, teams=[team])
  247. query = DiscoverSavedQuery.objects.create(
  248. organization=self.org,
  249. created_by_id=self.user.id,
  250. name="Test query",
  251. query={"fields": ["test"], "conditions": [], "limit": 10},
  252. )
  253. query.set_projects([project.id])
  254. with self.feature(self.feature_name):
  255. url = reverse(
  256. "sentry-api-0-discover-saved-query-detail",
  257. args=[self.org.slug, query.id],
  258. )
  259. response = self.client.put(url, {"name": "New query", "projects": [], "range": "24h"})
  260. assert response.status_code == 400
  261. assert "No Projects found, join a Team" == response.data["detail"]
  262. def test_put_homepage_query(self):
  263. query = {"fields": ["event_id"], "query": "event.type:error", "limit": 10, "version": 2}
  264. model = DiscoverSavedQuery.objects.create(
  265. organization=self.org,
  266. created_by_id=self.user.id,
  267. name="v2 query",
  268. query=query,
  269. is_homepage=True,
  270. )
  271. model.set_projects(self.project_ids)
  272. with self.feature(self.feature_name):
  273. url = reverse(
  274. "sentry-api-0-discover-saved-query-detail",
  275. args=[self.org.slug, model.id],
  276. )
  277. response = self.client.put(
  278. url, {"name": "New query", "projects": [], "range": "24h", "fields": []}
  279. )
  280. assert response.status_code == 404, response.content
  281. def test_put_org_without_access(self):
  282. with self.feature(self.feature_name):
  283. url = reverse(
  284. "sentry-api-0-discover-saved-query-detail",
  285. args=[self.org_without_access.slug, self.query_id],
  286. )
  287. response = self.client.put(
  288. url, {"name": "New query", "projects": self.project_ids, "range": "24h"}
  289. )
  290. assert response.status_code == 403, response.content
  291. def test_put_disallow_when_no_project_access(self):
  292. self.setup_no_team_user()
  293. with self.feature(self.feature_name):
  294. url = reverse(
  295. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  296. )
  297. response = self.client.put(
  298. url,
  299. {
  300. "name": "New query",
  301. "projects": self.project_ids,
  302. "fields": [],
  303. "range": "24h",
  304. "limit": 20,
  305. "conditions": [],
  306. "aggregations": [],
  307. "orderby": "-time",
  308. },
  309. )
  310. assert response.status_code == 403, response.data
  311. assert response.data == {"detail": "You do not have permission to perform this action."}
  312. def test_delete(self):
  313. with self.feature(self.feature_name):
  314. url = reverse(
  315. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  316. )
  317. response = self.client.delete(url)
  318. assert response.status_code == 204
  319. assert self.client.get(url).status_code == 404
  320. def test_delete_removes_projects(self):
  321. with self.feature(self.feature_name):
  322. url = reverse(
  323. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  324. )
  325. self.client.delete(url)
  326. projects = list(
  327. DiscoverSavedQueryProject.objects.filter(discover_saved_query=self.query_id)
  328. )
  329. assert projects == []
  330. def test_delete_query_without_access(self):
  331. with self.feature(self.feature_name):
  332. url = reverse(
  333. "sentry-api-0-discover-saved-query-detail",
  334. args=[self.org.slug, self.query_id_without_access],
  335. )
  336. response = self.client.delete(url)
  337. assert response.status_code == 404
  338. def test_delete_org_without_access(self):
  339. with self.feature(self.feature_name):
  340. url = reverse(
  341. "sentry-api-0-discover-saved-query-detail",
  342. args=[self.org_without_access.slug, self.query_id],
  343. )
  344. response = self.client.delete(url)
  345. assert response.status_code == 403, response.content
  346. def test_delete_homepage_query(self):
  347. query = {"fields": ["event_id"], "query": "event.type:error", "limit": 10, "version": 2}
  348. model = DiscoverSavedQuery.objects.create(
  349. organization=self.org,
  350. created_by_id=self.user.id,
  351. name="v2 query",
  352. query=query,
  353. is_homepage=True,
  354. )
  355. model.set_projects(self.project_ids)
  356. with self.feature(self.feature_name):
  357. url = reverse(
  358. "sentry-api-0-discover-saved-query-detail",
  359. args=[self.org.slug, model.id],
  360. )
  361. response = self.client.delete(url)
  362. assert response.status_code == 404, response.content
  363. def test_delete_disallow_when_no_project_access(self):
  364. self.setup_no_team_user()
  365. with self.feature(self.feature_name):
  366. url = reverse(
  367. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id]
  368. )
  369. response = self.client.delete(url)
  370. assert response.status_code == 403, response.data
  371. assert response.data == {"detail": "You do not have permission to perform this action."}
  372. def test_disallow_delete_all_projects_savedquery_when_no_open_membership(self):
  373. self.setup_no_team_user()
  374. query = {"fields": ["event_id"], "query": "event.type:error", "limit": 10, "version": 2}
  375. model = DiscoverSavedQuery.objects.create(
  376. organization=self.org,
  377. created_by_id=self.user.id,
  378. name="v2 query",
  379. query=query,
  380. )
  381. assert not model.projects.exists()
  382. with self.feature(self.feature_name):
  383. url = reverse(
  384. "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, model.id]
  385. )
  386. response = self.client.delete(url)
  387. assert response.status_code == 403, response.data
  388. assert response.data == {"detail": "You do not have permission to perform this action."}
  389. class OrganizationDiscoverQueryVisitTest(APITestCase, SnubaTestCase):
  390. def setUp(self):
  391. super().setUp()
  392. self.login_as(user=self.user)
  393. self.org = self.create_organization(owner=self.user)
  394. self.org_without_access = self.create_organization()
  395. self.project_ids = [
  396. self.create_project(organization=self.org).id,
  397. self.create_project(organization=self.org).id,
  398. ]
  399. q = {"fields": ["test"], "conditions": [], "limit": 10}
  400. self.query = DiscoverSavedQuery.objects.create(
  401. organization=self.org, created_by_id=self.user.id, name="Test query", query=q
  402. )
  403. self.query.set_projects(self.project_ids)
  404. def url(self, query_id):
  405. return reverse(
  406. "sentry-api-0-discover-saved-query-visit",
  407. kwargs={"organization_id_or_slug": self.org.slug, "query_id": query_id},
  408. )
  409. def test_visit_query(self):
  410. last_visited = self.query.last_visited
  411. assert last_visited is not None
  412. assert self.query.visits == 1
  413. with self.feature("organizations:discover-query"):
  414. response = self.client.post(self.url(self.query.id))
  415. assert response.status_code == 204
  416. query = DiscoverSavedQuery.objects.get(id=self.query.id)
  417. assert query.visits == 2
  418. assert query.last_visited is not None
  419. assert query.last_visited > last_visited
  420. def test_visit_query_no_access(self):
  421. last_visited = self.query.last_visited
  422. assert self.query.visits == 1
  423. with self.feature({"organizations:discover-query": False}):
  424. response = self.client.post(self.url(self.query.id))
  425. assert response.status_code == 404
  426. query = DiscoverSavedQuery.objects.get(id=self.query.id)
  427. assert query.visits == 1
  428. assert query.last_visited == last_visited