test_organization_events_meta.py 14 KB

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