test_organization_events_meta.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. from datetime import timezone
  2. from unittest import mock
  3. import pytest
  4. from django.urls import reverse
  5. from rest_framework.exceptions import ParseError
  6. from sentry.issues.grouptype import ProfileFileIOGroupType
  7. from sentry.testutils.cases import APITestCase, SnubaTestCase
  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. pytestmark = pytest.mark.sentry_metrics
  12. @region_silo_test
  13. class OrganizationEventsMetaEndpoint(APITestCase, SnubaTestCase, SearchIssueTestMixin):
  14. def setUp(self):
  15. super().setUp()
  16. self.min_ago = before_now(minutes=1)
  17. self.login_as(user=self.user)
  18. self.project = self.create_project()
  19. self.url = reverse(
  20. "sentry-api-0-organization-events-meta",
  21. kwargs={"organization_slug": self.project.organization.slug},
  22. )
  23. self.features = {"organizations:discover-basic": True}
  24. def test_simple(self):
  25. self.store_event(data={"timestamp": iso_format(self.min_ago)}, project_id=self.project.id)
  26. with self.feature(self.features):
  27. response = self.client.get(self.url, format="json")
  28. assert response.status_code == 200, response.content
  29. assert response.data["count"] == 1
  30. def test_multiple_projects(self):
  31. project2 = self.create_project()
  32. self.store_event(data={"timestamp": iso_format(self.min_ago)}, project_id=self.project.id)
  33. self.store_event(data={"timestamp": iso_format(self.min_ago)}, project_id=project2.id)
  34. response = self.client.get(self.url, format="json")
  35. assert response.status_code == 400, response.content
  36. self.features["organizations:global-views"] = True
  37. with self.feature(self.features):
  38. response = self.client.get(self.url, format="json")
  39. assert response.status_code == 200, response.content
  40. assert response.data["count"] == 2
  41. def test_search(self):
  42. self.store_event(
  43. data={"timestamp": iso_format(self.min_ago), "message": "how to make fast"},
  44. project_id=self.project.id,
  45. )
  46. self.store_event(
  47. data={"timestamp": iso_format(self.min_ago), "message": "Delete the Data"},
  48. project_id=self.project.id,
  49. )
  50. with self.feature(self.features):
  51. response = self.client.get(self.url, {"query": "delete"}, format="json")
  52. assert response.status_code == 200, response.content
  53. assert response.data["count"] == 1
  54. def test_invalid_query(self):
  55. with self.feature(self.features):
  56. response = self.client.get(self.url, {"query": "is:unresolved"}, format="json")
  57. assert response.status_code == 400, response.content
  58. def test_no_projects(self):
  59. no_project_org = self.create_organization(owner=self.user)
  60. url = reverse(
  61. "sentry-api-0-organization-events-meta",
  62. kwargs={"organization_slug": no_project_org.slug},
  63. )
  64. with self.feature(self.features):
  65. response = self.client.get(url, format="json")
  66. assert response.status_code == 200, response.content
  67. assert response.data["count"] == 0
  68. def test_transaction_event(self):
  69. data = {
  70. "event_id": "a" * 32,
  71. "type": "transaction",
  72. "transaction": "api.issue.delete",
  73. "spans": [],
  74. "contexts": {"trace": {"op": "foobar", "trace_id": "a" * 32, "span_id": "a" * 16}},
  75. "tags": {"important": "yes"},
  76. "timestamp": iso_format(before_now(minutes=1)),
  77. "start_timestamp": iso_format(before_now(minutes=1, seconds=3)),
  78. }
  79. self.store_event(data=data, project_id=self.project.id)
  80. url = reverse(
  81. "sentry-api-0-organization-events-meta",
  82. kwargs={"organization_slug": self.project.organization.slug},
  83. )
  84. with self.feature(self.features):
  85. response = self.client.get(url, {"query": "transaction.duration:>1"}, format="json")
  86. assert response.status_code == 200, response.content
  87. assert response.data["count"] == 1
  88. def test_generic_event(self):
  89. """Test that the issuePlatform dataset returns data for a generic issue's short ID"""
  90. _, _, group_info = self.store_search_issue(
  91. self.project.id,
  92. self.user.id,
  93. [f"{ProfileFileIOGroupType.type_id}-group1"],
  94. "prod",
  95. before_now(hours=1).replace(tzinfo=timezone.utc),
  96. )
  97. assert group_info is not None
  98. url = reverse(
  99. "sentry-api-0-organization-events-meta",
  100. kwargs={"organization_slug": self.project.organization.slug},
  101. )
  102. with self.feature(self.features):
  103. response = self.client.get(
  104. url,
  105. {
  106. "query": f"issue:{group_info.group.qualified_short_id}",
  107. "dataset": "issuePlatform",
  108. },
  109. format="json",
  110. )
  111. assert response.status_code == 200, response.content
  112. assert response.data["count"] == 1
  113. def test_transaction_event_with_last_seen(self):
  114. data = {
  115. "event_id": "a" * 32,
  116. "type": "transaction",
  117. "transaction": "api.issue.delete",
  118. "spans": [],
  119. "contexts": {"trace": {"op": "foobar", "trace_id": "a" * 32, "span_id": "a" * 16}},
  120. "tags": {"important": "yes"},
  121. "timestamp": iso_format(before_now(minutes=1)),
  122. "start_timestamp": iso_format(before_now(minutes=1, seconds=3)),
  123. }
  124. self.store_event(data=data, project_id=self.project.id)
  125. with self.feature(self.features):
  126. response = self.client.get(
  127. self.url, {"query": "event.type:transaction last_seen():>2012-12-31"}, format="json"
  128. )
  129. assert response.status_code == 200, response.content
  130. assert response.data["count"] == 1
  131. def test_out_of_retention(self):
  132. with self.feature(self.features):
  133. with self.options({"system.event-retention-days": 10}):
  134. response = self.client.get(
  135. self.url,
  136. format="json",
  137. data={
  138. "start": iso_format(before_now(days=20)),
  139. "end": iso_format(before_now(days=15)),
  140. },
  141. )
  142. assert response.status_code == 400
  143. @mock.patch("sentry.search.events.builder.discover.raw_snql_query")
  144. def test_handling_snuba_errors(self, mock_snql_query):
  145. mock_snql_query.side_effect = ParseError("test")
  146. with self.feature(self.features):
  147. response = self.client.get(self.url, format="json")
  148. assert response.status_code == 400, response.content
  149. @mock.patch("sentry.utils.snuba.quantize_time")
  150. def test_quantize_dates(self, mock_quantize):
  151. mock_quantize.return_value = before_now(days=1).replace(tzinfo=timezone.utc)
  152. with self.feature(self.features):
  153. # Don't quantize short time periods
  154. self.client.get(
  155. self.url,
  156. format="json",
  157. data={"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]},
  158. )
  159. # Don't quantize absolute date periods
  160. self.client.get(
  161. self.url,
  162. format="json",
  163. data={
  164. "start": iso_format(before_now(days=20)),
  165. "end": iso_format(before_now(days=15)),
  166. "query": "",
  167. "field": ["id", "timestamp"],
  168. },
  169. )
  170. assert len(mock_quantize.mock_calls) == 0
  171. # Quantize long date periods
  172. self.client.get(
  173. self.url,
  174. format="json",
  175. data={"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""},
  176. )
  177. assert len(mock_quantize.mock_calls) == 2
  178. @region_silo_test
  179. class OrganizationEventsRelatedIssuesEndpoint(APITestCase, SnubaTestCase):
  180. def setUp(self):
  181. super().setUp()
  182. def test_find_related_issue(self):
  183. self.login_as(user=self.user)
  184. project = self.create_project()
  185. event1 = self.store_event(
  186. data={"timestamp": iso_format(before_now(minutes=1)), "transaction": "/beth/sanchez"},
  187. project_id=project.id,
  188. )
  189. url = reverse(
  190. "sentry-api-0-organization-related-issues",
  191. kwargs={"organization_slug": project.organization.slug},
  192. )
  193. response = self.client.get(url, {"transaction": "/beth/sanchez"}, format="json")
  194. assert response.status_code == 200, response.content
  195. assert len(response.data) == 1
  196. assert response.data[0]["shortId"] == event1.group.qualified_short_id
  197. assert int(response.data[0]["id"]) == event1.group_id
  198. def test_related_issues_no_transaction(self):
  199. self.login_as(user=self.user)
  200. project = self.create_project()
  201. self.store_event(
  202. data={"timestamp": iso_format(before_now(minutes=1)), "transaction": "/beth/sanchez"},
  203. project_id=project.id,
  204. )
  205. url = reverse(
  206. "sentry-api-0-organization-related-issues",
  207. kwargs={"organization_slug": project.organization.slug},
  208. )
  209. response = self.client.get(url, format="json")
  210. assert response.status_code == 400, response.content
  211. assert (
  212. response.data["detail"]
  213. == "Must provide one of ['transaction'] in order to find related events"
  214. )
  215. def test_related_issues_no_matching_groups(self):
  216. self.login_as(user=self.user)
  217. project = self.create_project()
  218. self.store_event(
  219. data={"timestamp": iso_format(before_now(minutes=1)), "transaction": "/beth/sanchez"},
  220. project_id=project.id,
  221. )
  222. url = reverse(
  223. "sentry-api-0-organization-related-issues",
  224. kwargs={"organization_slug": project.organization.slug},
  225. )
  226. response = self.client.get(url, {"transaction": "/morty/sanchez"}, format="json")
  227. assert response.status_code == 200, response.content
  228. assert len(response.data) == 0
  229. def test_related_issues_only_issues_in_date(self):
  230. self.login_as(user=self.user)
  231. project = self.create_project()
  232. self.store_event(
  233. data={
  234. "event_id": "a" * 32,
  235. "timestamp": iso_format(before_now(days=2)),
  236. "transaction": "/beth/sanchez",
  237. },
  238. project_id=project.id,
  239. )
  240. event2 = self.store_event(
  241. data={
  242. "event_id": "b" * 32,
  243. "timestamp": iso_format(before_now(minutes=1)),
  244. "transaction": "/beth/sanchez",
  245. },
  246. project_id=project.id,
  247. )
  248. url = reverse(
  249. "sentry-api-0-organization-related-issues",
  250. kwargs={"organization_slug": project.organization.slug},
  251. )
  252. response = self.client.get(
  253. url, {"transaction": "/beth/sanchez", "statsPeriod": "24h"}, format="json"
  254. )
  255. assert response.status_code == 200, response.content
  256. assert len(response.data) == 1
  257. assert response.data[0]["shortId"] == event2.group.qualified_short_id
  258. assert int(response.data[0]["id"]) == event2.group_id
  259. def test_related_issues_transactions_from_different_projects(self):
  260. self.login_as(user=self.user)
  261. project1 = self.create_project()
  262. project2 = self.create_project()
  263. event1 = self.store_event(
  264. data={
  265. "event_id": "a" * 32,
  266. "timestamp": iso_format(before_now(minutes=1)),
  267. "transaction": "/beth/sanchez",
  268. },
  269. project_id=project1.id,
  270. )
  271. self.store_event(
  272. data={
  273. "event_id": "b" * 32,
  274. "timestamp": iso_format(before_now(minutes=1)),
  275. "transaction": "/beth/sanchez",
  276. },
  277. project_id=project2.id,
  278. )
  279. url = reverse(
  280. "sentry-api-0-organization-related-issues",
  281. kwargs={"organization_slug": project1.organization.slug},
  282. )
  283. response = self.client.get(
  284. url,
  285. {"transaction": "/beth/sanchez", "project": project1.id},
  286. format="json",
  287. )
  288. assert response.status_code == 200, response.content
  289. assert len(response.data) == 1
  290. assert response.data[0]["shortId"] == event1.group.qualified_short_id
  291. assert int(response.data[0]["id"]) == event1.group_id
  292. def test_related_issues_transactions_with_quotes(self):
  293. self.login_as(user=self.user)
  294. project = self.create_project()
  295. event = self.store_event(
  296. data={
  297. "event_id": "a" * 32,
  298. "timestamp": iso_format(before_now(minutes=1)),
  299. "transaction": '/beth/"sanchez"',
  300. },
  301. project_id=project.id,
  302. )
  303. url = reverse(
  304. "sentry-api-0-organization-related-issues",
  305. kwargs={"organization_slug": project.organization.slug},
  306. )
  307. response = self.client.get(
  308. url,
  309. {"transaction": '/beth/"sanchez"', "project": project.id},
  310. format="json",
  311. )
  312. assert response.status_code == 200, response.content
  313. assert len(response.data) == 1
  314. assert response.data[0]["shortId"] == event.group.qualified_short_id
  315. assert int(response.data[0]["id"]) == event.group_id
  316. url = reverse(
  317. "sentry-api-0-organization-related-issues",
  318. kwargs={"organization_slug": project.organization.slug},
  319. )
  320. response = self.client.get(
  321. url,
  322. {"transaction": '/beth/\\"sanchez\\"', "project": project.id},
  323. format="json",
  324. )
  325. assert response.status_code == 200, response.content
  326. assert len(response.data) == 1
  327. assert response.data[0]["shortId"] == event.group.qualified_short_id
  328. assert int(response.data[0]["id"]) == event.group_id