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