test_organization_events_meta.py 13 KB

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