test_group_events.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. from datetime import timedelta
  2. from django.utils import timezone
  3. from sentry.issues.grouptype import ProfileFileIOGroupType
  4. from sentry.testutils.cases import APITestCase, PerformanceIssueTestCase, SnubaTestCase
  5. from sentry.testutils.helpers import parse_link_header
  6. from sentry.testutils.helpers.datetime import before_now, freeze_time, iso_format
  7. from sentry.testutils.silo import region_silo_test
  8. from tests.sentry.issues.test_utils import SearchIssueTestMixin
  9. @region_silo_test
  10. class GroupEventsTest(APITestCase, SnubaTestCase, SearchIssueTestMixin, PerformanceIssueTestCase):
  11. def setUp(self):
  12. super().setUp()
  13. self.min_ago = before_now(minutes=1)
  14. self.two_min_ago = before_now(minutes=2)
  15. self.features = {}
  16. def do_request(self, url):
  17. with self.feature(self.features):
  18. return self.client.get(url, format="json")
  19. def _parse_links(self, header):
  20. # links come in {url: {...attrs}}, but we need {rel: {...attrs}}
  21. links = {}
  22. for url, attrs in parse_link_header(header).items():
  23. links[attrs["rel"]] = attrs
  24. attrs["href"] = url
  25. return links
  26. def test_simple(self):
  27. self.login_as(user=self.user)
  28. event_1 = self.store_event(
  29. data={
  30. "event_id": "a" * 32,
  31. "fingerprint": ["1"],
  32. "timestamp": iso_format(self.min_ago),
  33. },
  34. project_id=self.project.id,
  35. )
  36. event_2 = self.store_event(
  37. data={
  38. "event_id": "b" * 32,
  39. "fingerprint": ["1"],
  40. "timestamp": iso_format(self.min_ago),
  41. },
  42. project_id=self.project.id,
  43. )
  44. url = f"/api/0/issues/{event_1.group.id}/events/"
  45. response = self.do_request(url)
  46. assert response.status_code == 200, response.content
  47. assert len(response.data) == 2
  48. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
  49. [str(event_1.event_id), str(event_2.event_id)]
  50. )
  51. # Should default to full=false which does not include context property
  52. assert "context" not in response.data[0]
  53. assert "context" not in response.data[1]
  54. def test_full_false(self):
  55. self.login_as(user=self.user)
  56. event_1 = self.store_event(
  57. data={
  58. "event_id": "a" * 32,
  59. "fingerprint": ["1"],
  60. "timestamp": iso_format(self.min_ago),
  61. },
  62. project_id=self.project.id,
  63. )
  64. event_2 = self.store_event(
  65. data={
  66. "event_id": "b" * 32,
  67. "fingerprint": ["1"],
  68. "timestamp": iso_format(self.min_ago),
  69. },
  70. project_id=self.project.id,
  71. )
  72. url = f"/api/0/issues/{event_1.group.id}/events/?full=false"
  73. response = self.do_request(url)
  74. assert response.status_code == 200, response.content
  75. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
  76. [str(event_1.event_id), str(event_2.event_id)]
  77. )
  78. # Simplified response does not have context property
  79. assert "context" not in response.data[0]
  80. assert "context" not in response.data[1]
  81. def test_full_true(self):
  82. self.login_as(user=self.user)
  83. event_1 = self.store_event(
  84. data={
  85. "event_id": "a" * 32,
  86. "fingerprint": ["1"],
  87. "timestamp": iso_format(self.min_ago),
  88. },
  89. project_id=self.project.id,
  90. )
  91. self.store_event(
  92. data={
  93. "event_id": "b" * 32,
  94. "fingerprint": ["1"],
  95. "timestamp": iso_format(self.min_ago),
  96. },
  97. project_id=self.project.id,
  98. )
  99. url = f"/api/0/issues/{event_1.group.id}/events/?full=true"
  100. response = self.do_request(url)
  101. assert response.status_code == 200, response.content
  102. # Full response has context property
  103. assert "context" in response.data[0]
  104. assert "context" in response.data[1]
  105. def test_tags(self):
  106. self.login_as(user=self.user)
  107. event_1 = self.store_event(
  108. data={
  109. "event_id": "a" * 32,
  110. "fingerprint": ["1"],
  111. "tags": {"foo": "baz", "bar": "buz"},
  112. "timestamp": iso_format(self.min_ago),
  113. },
  114. project_id=self.project.id,
  115. )
  116. event_2 = self.store_event(
  117. data={
  118. "event_id": "b" * 32,
  119. "fingerprint": ["1"],
  120. "tags": {"bar": "biz"},
  121. "timestamp": iso_format(before_now(seconds=61)),
  122. },
  123. project_id=self.project.id,
  124. )
  125. url = f"/api/0/issues/{event_1.group.id}/events/"
  126. response = self.do_request(url + "?query=foo:baz")
  127. assert response.status_code == 200, response.content
  128. assert len(response.data) == 1
  129. assert response.data[0]["eventID"] == str(event_1.event_id)
  130. response = self.do_request(url + "?query=!foo:baz")
  131. assert response.status_code == 200, response.content
  132. assert len(response.data) == 1
  133. assert response.data[0]["eventID"] == str(event_2.event_id)
  134. response = self.do_request(url + "?query=bar:biz")
  135. assert response.status_code == 200, response.content
  136. assert len(response.data) == 1
  137. assert response.data[0]["eventID"] == str(event_2.event_id)
  138. response = self.do_request(url + "?query=bar:biz%20foo:baz")
  139. assert response.status_code == 200, response.content
  140. assert len(response.data) == 0
  141. response = self.do_request(url + "?query=bar:buz%20foo:baz")
  142. assert response.status_code == 200, response.content
  143. assert len(response.data) == 1
  144. assert response.data[0]["eventID"] == str(event_1.event_id)
  145. response = self.do_request(url + "?query=bar:baz")
  146. assert response.status_code == 200, response.content
  147. assert len(response.data) == 0
  148. response = self.do_request(url + "?query=a:b")
  149. assert response.status_code == 200, response.content
  150. assert len(response.data) == 0
  151. response = self.do_request(url + "?query=bar:b")
  152. assert response.status_code == 200, response.content
  153. assert len(response.data) == 0
  154. response = self.do_request(url + "?query=bar:baz")
  155. assert response.status_code == 200, response.content
  156. assert len(response.data) == 0
  157. response = self.do_request(url + "?query=!bar:baz")
  158. assert response.status_code == 200, response.content
  159. assert len(response.data) == 2
  160. assert {e["eventID"] for e in response.data} == {event_1.event_id, event_2.event_id}
  161. def test_search_event_by_id(self):
  162. self.login_as(user=self.user)
  163. event_1 = self.store_event(
  164. data={
  165. "event_id": "a" * 32,
  166. "fingerprint": ["group-1"],
  167. "timestamp": iso_format(self.min_ago),
  168. },
  169. project_id=self.project.id,
  170. )
  171. self.store_event(
  172. data={
  173. "event_id": "b" * 32,
  174. "fingerprint": ["group-1"],
  175. "timestamp": iso_format(self.min_ago),
  176. },
  177. project_id=self.project.id,
  178. )
  179. url = f"/api/0/issues/{event_1.group.id}/events/?query={event_1.event_id}"
  180. response = self.do_request(url)
  181. assert response.status_code == 200, response.content
  182. assert len(response.data) == 1
  183. assert response.data[0]["eventID"] == event_1.event_id
  184. def test_search_event_by_message(self):
  185. self.login_as(user=self.user)
  186. event_1 = self.store_event(
  187. data={
  188. "event_id": "a" * 32,
  189. "fingerprint": ["group-1"],
  190. "message": "foo bar hello world",
  191. "timestamp": iso_format(self.min_ago),
  192. },
  193. project_id=self.project.id,
  194. )
  195. group = event_1.group
  196. event_2 = self.store_event(
  197. data={
  198. "event_id": "b" * 32,
  199. "fingerprint": ["group-1"],
  200. "message": "this bar hello world",
  201. "timestamp": iso_format(self.min_ago),
  202. },
  203. project_id=self.project.id,
  204. )
  205. assert group == event_2.group
  206. query_1 = "foo"
  207. query_2 = "hello+world"
  208. # Single Word Query
  209. url = f"/api/0/issues/{group.id}/events/?query={query_1}"
  210. response = self.do_request(url)
  211. assert response.status_code == 200, response.content
  212. assert len(response.data) == 1
  213. assert response.data[0]["eventID"] == event_1.event_id
  214. # Multiple Word Query
  215. url = f"/api/0/issues/{group.id}/events/?query={query_2}"
  216. response = self.do_request(url)
  217. assert response.status_code == 200, response.content
  218. assert len(response.data) == 2
  219. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
  220. [str(event_1.event_id), str(event_2.event_id)]
  221. )
  222. def test_search_by_release(self):
  223. self.login_as(user=self.user)
  224. self.create_release(self.project, version="first-release")
  225. event_1 = self.store_event(
  226. data={
  227. "event_id": "a" * 32,
  228. "fingerprint": ["group-1"],
  229. "timestamp": iso_format(self.min_ago),
  230. "release": "first-release",
  231. },
  232. project_id=self.project.id,
  233. )
  234. url = f"/api/0/issues/{event_1.group.id}/events/?query=release:latest"
  235. response = self.do_request(url)
  236. assert response.status_code == 200, response.content
  237. assert len(response.data) == 1
  238. assert response.data[0]["eventID"] == event_1.event_id
  239. def test_environment(self):
  240. self.login_as(user=self.user)
  241. events = {}
  242. for name in ["production", "development"]:
  243. events[name] = self.store_event(
  244. data={
  245. "fingerprint": ["put-me-in-group1"],
  246. "timestamp": iso_format(self.min_ago),
  247. "environment": name,
  248. },
  249. project_id=self.project.id,
  250. )
  251. # Asserts that all are in the same group
  252. (group_id,) = {e.group.id for e in events.values()}
  253. url = f"/api/0/issues/{group_id}/events/"
  254. response = self.do_request(url + "?environment=production")
  255. assert response.status_code == 200, response.content
  256. assert set(map(lambda x: x["eventID"], response.data)) == {
  257. str(events["production"].event_id)
  258. }
  259. response = self.client.get(
  260. url, data={"environment": ["production", "development"]}, format="json"
  261. )
  262. assert response.status_code == 200, response.content
  263. assert set(map(lambda x: x["eventID"], response.data)) == {
  264. str(event.event_id) for event in events.values()
  265. }
  266. response = self.do_request(url + "?environment=invalid")
  267. assert response.status_code == 200, response.content
  268. assert response.data == []
  269. response = self.client.get(
  270. url + "?environment=production&query=environment:development", format="json"
  271. )
  272. assert response.status_code == 200, response.content
  273. assert response.data == []
  274. def test_filters_based_on_retention(self):
  275. self.login_as(user=self.user)
  276. self.store_event(
  277. data={"fingerprint": ["group_1"], "timestamp": iso_format(before_now(days=2))},
  278. project_id=self.project.id,
  279. )
  280. event_2 = self.store_event(
  281. data={"fingerprint": ["group_1"], "timestamp": iso_format(self.min_ago)},
  282. project_id=self.project.id,
  283. )
  284. group = event_2.group
  285. with self.options({"system.event-retention-days": 1}):
  286. response = self.client.get(f"/api/0/issues/{group.id}/events/")
  287. assert response.status_code == 200, response.content
  288. assert len(response.data) == 1
  289. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted([str(event_2.event_id)])
  290. def test_search_event_has_tags(self):
  291. self.login_as(user=self.user)
  292. event = self.store_event(
  293. data={
  294. "timestamp": iso_format(self.min_ago),
  295. "message": "foo",
  296. "tags": {"logger": "python"},
  297. },
  298. project_id=self.project.id,
  299. )
  300. response = self.client.get(f"/api/0/issues/{event.group.id}/events/")
  301. assert response.status_code == 200, response.content
  302. assert len(response.data) == 1
  303. assert {"key": "logger", "value": "python"} in response.data[0]["tags"]
  304. @freeze_time()
  305. def test_date_filters(self):
  306. self.login_as(user=self.user)
  307. event_1 = self.store_event(
  308. data={"timestamp": iso_format(before_now(days=5)), "fingerprint": ["group-1"]},
  309. project_id=self.project.id,
  310. )
  311. event_2 = self.store_event(
  312. data={"timestamp": iso_format(before_now(days=1)), "fingerprint": ["group-1"]},
  313. project_id=self.project.id,
  314. )
  315. group = event_1.group
  316. assert group == event_2.group
  317. response = self.client.get(f"/api/0/issues/{group.id}/events/", data={"statsPeriod": "6d"})
  318. assert response.status_code == 200, response.content
  319. assert len(response.data) == 2
  320. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
  321. [str(event_1.event_id), str(event_2.event_id)]
  322. )
  323. response = self.client.get(f"/api/0/issues/{group.id}/events/", data={"statsPeriod": "2d"})
  324. assert response.status_code == 200, response.content
  325. assert len(response.data) == 1
  326. assert response.data[0]["eventID"] == str(event_2.event_id)
  327. def test_invalid_period(self):
  328. self.login_as(user=self.user)
  329. first_seen = timezone.now() - timedelta(days=5)
  330. group = self.create_group(first_seen=first_seen)
  331. response = self.client.get(f"/api/0/issues/{group.id}/events/", data={"statsPeriod": "lol"})
  332. assert response.status_code == 400
  333. def test_invalid_query(self):
  334. self.login_as(user=self.user)
  335. first_seen = timezone.now() - timedelta(days=5)
  336. group = self.create_group(first_seen=first_seen)
  337. response = self.client.get(
  338. f"/api/0/issues/{group.id}/events/",
  339. data={"statsPeriod": "7d", "query": "foo(bar"},
  340. )
  341. assert response.status_code == 400
  342. def test_multiple_group(self):
  343. self.login_as(user=self.user)
  344. event_1 = self.store_event(
  345. data={
  346. "fingerprint": ["group_1"],
  347. "event_id": "a" * 32,
  348. "message": "foo",
  349. "timestamp": iso_format(self.min_ago),
  350. },
  351. project_id=self.project.id,
  352. )
  353. event_2 = self.store_event(
  354. data={
  355. "fingerprint": ["group_2"],
  356. "event_id": "b" * 32,
  357. "message": "group2",
  358. "timestamp": iso_format(self.min_ago),
  359. },
  360. project_id=self.project.id,
  361. )
  362. for event in (event_1, event_2):
  363. url = f"/api/0/issues/{event.group.id}/events/"
  364. response = self.do_request(url)
  365. assert response.status_code == 200, response.content
  366. assert len(response.data) == 1, response.data
  367. assert list(map(lambda x: x["eventID"], response.data)) == [str(event.event_id)]
  368. def test_pagination(self):
  369. self.login_as(user=self.user)
  370. for _ in range(2):
  371. event = self.store_event(
  372. data={
  373. "fingerprint": ["group_1"],
  374. "event_id": "a" * 32,
  375. "message": "foo",
  376. "timestamp": iso_format(self.min_ago),
  377. },
  378. project_id=self.project.id,
  379. )
  380. url = f"/api/0/issues/{event.group.id}/events/?per_page=1"
  381. response = self.do_request(url)
  382. links = self._parse_links(response["Link"])
  383. assert response.status_code == 200, response.content
  384. assert links["previous"]["results"] == "false"
  385. assert links["next"]["results"] == "true"
  386. assert len(response.data) == 1
  387. def test_orderby(self):
  388. self.login_as(user=self.user)
  389. event = self.store_event(
  390. data={
  391. "fingerprint": ["group_1"],
  392. "event_id": "a" * 32,
  393. "message": "foo",
  394. "timestamp": iso_format(self.min_ago),
  395. },
  396. project_id=self.project.id,
  397. )
  398. event = self.store_event(
  399. data={
  400. "fingerprint": ["group_1"],
  401. "event_id": "b" * 32,
  402. "message": "foo",
  403. "timestamp": iso_format(self.two_min_ago),
  404. },
  405. project_id=self.project.id,
  406. )
  407. url = f"/api/0/issues/{event.group.id}/events/"
  408. response = self.do_request(url)
  409. assert len(response.data) == 2
  410. assert response.data[0]["eventID"] == "a" * 32
  411. assert response.data[1]["eventID"] == "b" * 32
  412. def test_perf_issue(self):
  413. event_1 = self.create_performance_issue()
  414. event_2 = self.create_performance_issue()
  415. self.login_as(user=self.user)
  416. url = f"/api/0/issues/{event_1.group.id}/events/"
  417. response = self.do_request(url)
  418. assert response.status_code == 200, response.content
  419. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
  420. [str(event_1.event_id), str(event_2.event_id)]
  421. )
  422. def test_generic_issue(self):
  423. event_1, _, group_info = self.store_search_issue(
  424. self.project.id,
  425. self.user.id,
  426. [f"{ProfileFileIOGroupType.type_id}-group1"],
  427. "prod",
  428. before_now(hours=1).replace(tzinfo=timezone.utc),
  429. )
  430. assert group_info is not None
  431. event_2, _, _ = self.store_search_issue(
  432. self.project.id,
  433. self.user.id,
  434. [f"{ProfileFileIOGroupType.type_id}-group1"],
  435. "prod",
  436. before_now(hours=1).replace(tzinfo=timezone.utc),
  437. )
  438. self.login_as(user=self.user)
  439. url = f"/api/0/issues/{group_info.group.id}/events/"
  440. response = self.do_request(url)
  441. assert response.status_code == 200, response.content
  442. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(
  443. [str(event_1.event_id), str(event_2.event_id)]
  444. )