test_organization_event_details.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. from datetime import timedelta
  2. import pytest
  3. from django.urls import NoReverseMatch, reverse
  4. from sentry.models.group import Group
  5. from sentry.search.events import constants
  6. from sentry.testutils.cases import APITestCase, MetricsEnhancedPerformanceTestCase, SnubaTestCase
  7. from sentry.testutils.helpers.datetime import before_now, iso_format
  8. from sentry.testutils.silo import region_silo_test
  9. from sentry.utils.samples import load_data
  10. from tests.sentry.issues.test_utils import OccurrenceTestMixin
  11. pytestmark = pytest.mark.sentry_metrics
  12. def format_project_event(project_slug, event_id):
  13. return f"{project_slug}:{event_id}"
  14. @region_silo_test
  15. class OrganizationEventDetailsEndpointTest(APITestCase, SnubaTestCase, OccurrenceTestMixin):
  16. def setUp(self):
  17. super().setUp()
  18. min_ago = iso_format(before_now(minutes=1))
  19. two_min_ago = iso_format(before_now(minutes=2))
  20. three_min_ago = iso_format(before_now(minutes=3))
  21. self.login_as(user=self.user)
  22. self.project = self.create_project()
  23. self.project_2 = self.create_project()
  24. self.store_event(
  25. data={
  26. "event_id": "a" * 32,
  27. "message": "oh no",
  28. "timestamp": three_min_ago,
  29. "fingerprint": ["group-1"],
  30. },
  31. project_id=self.project.id,
  32. )
  33. self.store_event(
  34. data={
  35. "event_id": "b" * 32,
  36. "message": "very bad",
  37. "timestamp": two_min_ago,
  38. "fingerprint": ["group-1"],
  39. },
  40. project_id=self.project.id,
  41. )
  42. self.store_event(
  43. data={
  44. "event_id": "c" * 32,
  45. "message": "very bad",
  46. "timestamp": min_ago,
  47. "fingerprint": ["group-2"],
  48. },
  49. project_id=self.project.id,
  50. )
  51. self.groups = list(Group.objects.all().order_by("id"))
  52. def test_performance_flag(self):
  53. url = reverse(
  54. "sentry-api-0-organization-event-details",
  55. kwargs={
  56. "organization_slug": self.project.organization.slug,
  57. "project_slug": self.project.slug,
  58. "event_id": "a" * 32,
  59. },
  60. )
  61. with self.feature(
  62. {"organizations:discover-basic": False, "organizations:performance-view": True}
  63. ):
  64. response = self.client.get(url, format="json")
  65. assert response.status_code == 200, response.content
  66. assert response.data["id"] == "a" * 32
  67. assert response.data["projectSlug"] == self.project.slug
  68. def test_simple(self):
  69. url = reverse(
  70. "sentry-api-0-organization-event-details",
  71. kwargs={
  72. "organization_slug": self.project.organization.slug,
  73. "project_slug": self.project.slug,
  74. "event_id": "a" * 32,
  75. },
  76. )
  77. with self.feature("organizations:discover-basic"):
  78. response = self.client.get(url, format="json")
  79. assert response.status_code == 200, response.content
  80. assert response.data["id"] == "a" * 32
  81. assert response.data["projectSlug"] == self.project.slug
  82. def test_simple_transaction(self):
  83. min_ago = iso_format(before_now(minutes=1))
  84. event = self.store_event(
  85. data={
  86. "event_id": "d" * 32,
  87. "type": "transaction",
  88. "transaction": "api.issue.delete",
  89. "spans": [],
  90. "contexts": {"trace": {"op": "foobar", "trace_id": "a" * 32, "span_id": "a" * 16}},
  91. "start_timestamp": iso_format(before_now(minutes=1, seconds=5)),
  92. "timestamp": min_ago,
  93. },
  94. project_id=self.project.id,
  95. )
  96. url = reverse(
  97. "sentry-api-0-organization-event-details",
  98. kwargs={
  99. "organization_slug": self.project.organization.slug,
  100. "project_slug": self.project.slug,
  101. "event_id": event.event_id,
  102. },
  103. )
  104. with self.feature("organizations:discover-basic"):
  105. response = self.client.get(url, format="json")
  106. assert response.status_code == 200
  107. assert response.data["id"] == "d" * 32
  108. assert response.data["type"] == "transaction"
  109. def test_no_access_missing_feature(self):
  110. with self.feature({"organizations:discover-basic": False}):
  111. url = reverse(
  112. "sentry-api-0-organization-event-details",
  113. kwargs={
  114. "organization_slug": self.project.organization.slug,
  115. "project_slug": self.project.slug,
  116. "event_id": "a" * 32,
  117. },
  118. )
  119. response = self.client.get(url, format="json")
  120. assert response.status_code == 404, response.content
  121. def test_access_non_member_project(self):
  122. # Add a new user to a project and then access events on project they are not part of.
  123. member_user = self.create_user()
  124. team = self.create_team(members=[member_user])
  125. self.create_project(organization=self.organization, teams=[team])
  126. # Enable open membership
  127. self.organization.flags.allow_joinleave = True
  128. self.organization.save()
  129. self.login_as(member_user)
  130. url = reverse(
  131. "sentry-api-0-organization-event-details",
  132. kwargs={
  133. "organization_slug": self.organization.slug,
  134. "project_slug": self.project.slug,
  135. "event_id": "a" * 32,
  136. },
  137. )
  138. with self.feature("organizations:discover-basic"):
  139. response = self.client.get(url, format="json")
  140. assert response.status_code == 200, response.content
  141. # When open membership is off, access should be denied to non owner users
  142. self.organization.flags.allow_joinleave = False
  143. self.organization.save()
  144. with self.feature("organizations:discover-basic"):
  145. response = self.client.get(url, format="json")
  146. assert response.status_code == 404, response.content
  147. def test_no_event(self):
  148. url = reverse(
  149. "sentry-api-0-organization-event-details",
  150. kwargs={
  151. "organization_slug": self.project.organization.slug,
  152. "project_slug": self.project.slug,
  153. "event_id": "d" * 32,
  154. },
  155. )
  156. with self.feature("organizations:discover-basic"):
  157. response = self.client.get(url, format="json")
  158. assert response.status_code == 404, response.content
  159. def test_invalid_event_id(self):
  160. with pytest.raises(NoReverseMatch):
  161. reverse(
  162. "sentry-api-0-organization-event-details",
  163. kwargs={
  164. "organization_slug": self.project.organization.slug,
  165. "project_slug": self.project.slug,
  166. "event_id": "not-an-event",
  167. },
  168. )
  169. def test_long_trace_description(self):
  170. data = load_data("transaction")
  171. data["event_id"] = "d" * 32
  172. data["timestamp"] = iso_format(before_now(minutes=1))
  173. data["start_timestamp"] = iso_format(before_now(minutes=1) - timedelta(seconds=5))
  174. data["contexts"]["trace"]["description"] = "b" * 512
  175. self.store_event(data=data, project_id=self.project.id)
  176. url = reverse(
  177. "sentry-api-0-organization-event-details",
  178. kwargs={
  179. "organization_slug": self.project.organization.slug,
  180. "project_slug": self.project.slug,
  181. "event_id": "d" * 32,
  182. },
  183. )
  184. with self.feature("organizations:discover-basic"):
  185. response = self.client.get(url, format="json")
  186. assert response.status_code == 200, response.content
  187. trace = response.data["contexts"]["trace"]
  188. original_trace = data["contexts"]["trace"]
  189. assert trace["trace_id"] == original_trace["trace_id"]
  190. assert trace["span_id"] == original_trace["span_id"]
  191. assert trace["parent_span_id"] == original_trace["parent_span_id"]
  192. assert trace["description"][:-3] in original_trace["description"]
  193. def test_blank_fields(self):
  194. url = reverse(
  195. "sentry-api-0-organization-event-details",
  196. kwargs={
  197. "organization_slug": self.project.organization.slug,
  198. "project_slug": self.project.slug,
  199. "event_id": "a" * 32,
  200. },
  201. )
  202. with self.feature("organizations:discover-basic"):
  203. response = self.client.get(
  204. url,
  205. data={"field": ["", " "], "statsPeriod": "24h"},
  206. format="json",
  207. )
  208. assert response.status_code == 200, response.content
  209. assert response.data["id"] == "a" * 32
  210. assert response.data["projectSlug"] == self.project.slug
  211. def test_out_of_retention(self):
  212. self.store_event(
  213. data={
  214. "event_id": "d" * 32,
  215. "message": "oh no",
  216. "timestamp": iso_format(before_now(days=2)),
  217. "fingerprint": ["group-1"],
  218. },
  219. project_id=self.project.id,
  220. )
  221. url = reverse(
  222. "sentry-api-0-organization-event-details",
  223. kwargs={
  224. "organization_slug": self.project.organization.slug,
  225. "project_slug": self.project.slug,
  226. "event_id": "d" * 32,
  227. },
  228. )
  229. with self.options({"system.event-retention-days": 1}):
  230. response = self.client.get(
  231. url,
  232. format="json",
  233. )
  234. assert response.status_code == 404, response.content
  235. def test_generic_event(self):
  236. occurrence, _ = self.process_occurrence(
  237. project_id=self.project.id,
  238. event_data={
  239. "level": "info",
  240. },
  241. )
  242. url = reverse(
  243. "sentry-api-0-organization-event-details",
  244. kwargs={
  245. "organization_slug": self.project.organization.slug,
  246. "project_slug": self.project.slug,
  247. "event_id": occurrence.event_id,
  248. },
  249. )
  250. with self.feature("organizations:discover-basic"):
  251. response = self.client.get(url, format="json")
  252. assert response.status_code == 200, response.content
  253. assert response.data["id"] == occurrence.event_id
  254. assert response.data["projectSlug"] == self.project.slug
  255. assert response.data["occurrence"] is not None
  256. assert response.data["occurrence"]["id"] == occurrence.id
  257. class EventComparisonTest(MetricsEnhancedPerformanceTestCase):
  258. endpoint = "sentry-api-0-organization-event-details"
  259. def setUp(self):
  260. self.init_snuba()
  261. self.ten_mins_ago = before_now(minutes=10)
  262. self.transaction_data = load_data("transaction", timestamp=self.ten_mins_ago)
  263. event = self.store_event(self.transaction_data, self.project)
  264. self.url = reverse(
  265. self.endpoint,
  266. kwargs={
  267. "organization_slug": self.project.organization.slug,
  268. "project_slug": self.project.slug,
  269. "event_id": event.event_id,
  270. },
  271. )
  272. self.login_as(user=self.user)
  273. self.store_span_metric(
  274. 1,
  275. internal_metric=constants.SELF_TIME_LIGHT,
  276. timestamp=self.ten_mins_ago,
  277. tags={"span.group": "26b881987e4bad99"},
  278. )
  279. def test_get(self):
  280. response = self.client.get(self.url, {"averageColumn": "span.self_time"})
  281. assert response.status_code == 200, response.content
  282. entries = response.data["entries"] # type: ignore[attr-defined]
  283. for entry in entries:
  284. if entry["type"] == "spans":
  285. for span in entry["data"]:
  286. if span["span_id"] == "26b881987e4bad99":
  287. assert span["span.average_time"] == 1.0
  288. if span["span_id"] == "c048b4fffdc4279d":
  289. assert "span.average_time" not in span