test_group_events.py 19 KB

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