test_organization_events_trends_v2.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. from datetime import timedelta
  2. from typing import Any, Dict, List, Union
  3. from unittest import mock
  4. import pytest
  5. from django.urls import reverse
  6. from freezegun import freeze_time
  7. from sentry.snuba.metrics.naming_layer import TransactionMRI
  8. from sentry.testutils.cases import MetricsAPIBaseTestCase
  9. from sentry.testutils.helpers.datetime import iso_format
  10. from sentry.testutils.silo import region_silo_test
  11. pytestmark = pytest.mark.sentry_metrics
  12. @region_silo_test(stable=True)
  13. @freeze_time(MetricsAPIBaseTestCase.MOCK_DATETIME)
  14. class OrganizationEventsTrendsStatsV2EndpointTest(MetricsAPIBaseTestCase):
  15. def setUp(self):
  16. super().setUp()
  17. self.login_as(self.user)
  18. self.org = self.create_organization(owner=self.user)
  19. self.project = self.create_project(organization=self.org)
  20. self.url = reverse("sentry-api-0-organization-events-trends-statsv2", args=[self.org.slug])
  21. self.store_performance_metric(
  22. name=TransactionMRI.DURATION.value,
  23. tags={"transaction": "foo"},
  24. org_id=self.org.id,
  25. project_id=self.project.id,
  26. value=1,
  27. hours_before_now=1,
  28. )
  29. self.store_performance_metric(
  30. name=TransactionMRI.DURATION.value,
  31. tags={"transaction": "foo"},
  32. org_id=self.org.id,
  33. project_id=self.project.id,
  34. value=2,
  35. hours_before_now=2,
  36. )
  37. self.store_performance_metric(
  38. name=TransactionMRI.DURATION.value,
  39. tags={"transaction": "foo"},
  40. org_id=self.org.id,
  41. project_id=self.project.id,
  42. value=2,
  43. hours_before_now=2,
  44. )
  45. self.features = {
  46. "organizations:performance-view": True,
  47. "organizations:performance-new-trends": True,
  48. }
  49. @property
  50. def now(self):
  51. return MetricsAPIBaseTestCase.MOCK_DATETIME
  52. def test_no_feature_flag(self):
  53. response = self.client.get(
  54. self.url,
  55. format="json",
  56. data={
  57. "end": iso_format(self.now - timedelta(minutes=1)),
  58. "start": iso_format(self.now - timedelta(hours=4)),
  59. "field": ["project", "transaction"],
  60. "query": "event.type:transaction",
  61. },
  62. )
  63. assert response.status_code == 404, response.content
  64. def test_no_project(self):
  65. with self.feature(self.features):
  66. response = self.client.get(
  67. self.url,
  68. format="json",
  69. data={
  70. "end": iso_format(self.now - timedelta(minutes=1)),
  71. "start": iso_format(self.now - timedelta(hours=4)),
  72. "interval": "1h",
  73. "field": ["project", "transaction"],
  74. "query": "",
  75. },
  76. )
  77. assert response.status_code == 200, response.content
  78. assert response.data == []
  79. @mock.patch("sentry.api.endpoints.organization_events_trends_v2.get_trends")
  80. def test_simple_with_trends(self, mock_get_trends):
  81. mock_trends_result = [
  82. {
  83. "project": self.project.slug,
  84. "transaction": "foo",
  85. "change": "regression",
  86. "trend_difference": -15,
  87. "trend_percentage": 0.88,
  88. }
  89. ]
  90. mock_get_trends.return_value = {"data": mock_trends_result}
  91. with self.feature(self.features):
  92. response = self.client.get(
  93. self.url,
  94. format="json",
  95. data={
  96. "end": iso_format(self.now),
  97. "start": iso_format(self.now - timedelta(days=1)),
  98. "interval": "1h",
  99. "field": ["project", "transaction"],
  100. "query": "event.type:transaction",
  101. "project": self.project.id,
  102. },
  103. )
  104. assert response.status_code == 200, response.content
  105. events = response.data["events"]
  106. result_stats = response.data["stats"]
  107. assert len(events["data"]) == 1
  108. assert events["data"] == mock_trends_result
  109. assert len(result_stats) > 0
  110. assert len(result_stats.get(f"{self.project.slug},foo", [])) > 0
  111. @mock.patch("sentry.api.endpoints.organization_events_trends_v2.get_trends")
  112. def test_simple_with_no_trends(self, mock_get_trends):
  113. mock_trends_result: List[Union[Dict[str, Any], None]] = []
  114. mock_get_trends.return_value = {"data": mock_trends_result}
  115. with self.feature(self.features):
  116. response = self.client.get(
  117. self.url,
  118. format="json",
  119. data={
  120. "end": iso_format(self.now),
  121. "start": iso_format(self.now - timedelta(days=1)),
  122. "interval": "1h",
  123. "field": ["project", "transaction"],
  124. "query": "event.type:transaction",
  125. "project": self.project.id,
  126. },
  127. )
  128. assert response.status_code == 200, response.content
  129. events = response.data["events"]
  130. result_stats = response.data["stats"]
  131. assert len(events["data"]) == 0
  132. assert events["data"] == []
  133. assert len(result_stats) == 0
  134. @mock.patch("sentry.api.endpoints.organization_events_trends_v2.get_trends")
  135. def test_simple_with_transaction_query(self, mock_get_trends):
  136. mock_trends_result: List[Union[Dict[str, Any], None]] = []
  137. mock_get_trends.return_value = {"data": mock_trends_result}
  138. self.store_performance_metric(
  139. name=TransactionMRI.DURATION.value,
  140. tags={"transaction": "bar"},
  141. org_id=self.org.id,
  142. project_id=self.project.id,
  143. value=2,
  144. hours_before_now=2,
  145. )
  146. with self.feature(self.features):
  147. response = self.client.get(
  148. self.url,
  149. format="json",
  150. data={
  151. "end": iso_format(self.now),
  152. "start": iso_format(self.now - timedelta(days=1)),
  153. "interval": "1h",
  154. "field": ["project", "transaction"],
  155. "query": "event.type:transaction transaction:foo",
  156. "project": self.project.id,
  157. },
  158. )
  159. trends_call_args_data = mock_get_trends.call_args[0][0]["data"]
  160. assert len(trends_call_args_data.get(f"{self.project.slug},foo")) > 0
  161. assert len(trends_call_args_data.get(f"{self.project.slug},bar", [])) == 0
  162. assert response.status_code == 200, response.content
  163. @mock.patch("sentry.api.endpoints.organization_events_trends_v2.get_trends")
  164. def test_simple_with_trends_p75(self, mock_get_trends):
  165. mock_trends_result = [
  166. {
  167. "project": self.project.slug,
  168. "transaction": "foo",
  169. "change": "regression",
  170. "trend_difference": -15,
  171. "trend_percentage": 0.88,
  172. }
  173. ]
  174. mock_get_trends.return_value = {"data": mock_trends_result}
  175. with self.feature(self.features):
  176. response = self.client.get(
  177. self.url,
  178. format="json",
  179. data={
  180. "end": iso_format(self.now),
  181. "start": iso_format(self.now - timedelta(days=1)),
  182. "interval": "1h",
  183. "field": ["project", "transaction"],
  184. "query": "event.type:transaction",
  185. "project": self.project.id,
  186. "trendFunction": "p75(transaction.duration)",
  187. },
  188. )
  189. assert response.status_code == 200, response.content
  190. events = response.data["events"]
  191. result_stats = response.data["stats"]
  192. assert len(events["data"]) == 1
  193. assert events["data"] == mock_trends_result
  194. assert len(result_stats) > 0
  195. assert len(result_stats.get(f"{self.project.slug},foo", [])) > 0
  196. @mock.patch("sentry.api.endpoints.organization_events_trends_v2.get_trends")
  197. def test_simple_with_trends_p95(self, mock_get_trends):
  198. mock_trends_result = [
  199. {
  200. "project": self.project.slug,
  201. "transaction": "foo",
  202. "change": "regression",
  203. "trend_difference": -15,
  204. "trend_percentage": 0.88,
  205. }
  206. ]
  207. mock_get_trends.return_value = {"data": mock_trends_result}
  208. with self.feature(self.features):
  209. response = self.client.get(
  210. self.url,
  211. format="json",
  212. data={
  213. "end": iso_format(self.now),
  214. "start": iso_format(self.now - timedelta(days=1)),
  215. "interval": "1h",
  216. "field": ["project", "transaction"],
  217. "query": "event.type:transaction",
  218. "project": self.project.id,
  219. "trendFunction": "p95(transaction.duration)",
  220. },
  221. )
  222. assert response.status_code == 200, response.content
  223. events = response.data["events"]
  224. result_stats = response.data["stats"]
  225. assert len(events["data"]) == 1
  226. assert events["data"] == mock_trends_result
  227. assert len(result_stats) > 0
  228. assert len(result_stats.get(f"{self.project.slug},foo", [])) > 0
  229. @mock.patch("sentry.api.endpoints.organization_events_trends_v2.get_trends")
  230. def test_simple_with_top_events(self, mock_get_trends):
  231. # store second metric but with lower count
  232. self.store_performance_metric(
  233. name=TransactionMRI.DURATION.value,
  234. tags={"transaction": "bar"},
  235. org_id=self.org.id,
  236. project_id=self.project.id,
  237. value=2,
  238. hours_before_now=2,
  239. )
  240. with self.feature(self.features):
  241. response = self.client.get(
  242. self.url,
  243. format="json",
  244. data={
  245. "end": iso_format(self.now),
  246. "start": iso_format(self.now - timedelta(days=1)),
  247. "interval": "1h",
  248. "field": ["project", "transaction"],
  249. "query": "event.type:transaction",
  250. "project": self.project.id,
  251. "trendFunction": "p95(transaction.duration)",
  252. "topEvents": 1,
  253. },
  254. )
  255. assert response.status_code == 200, response.content
  256. trends_call_args_data = mock_get_trends.call_args[0][0]["data"]
  257. assert len(trends_call_args_data.get(f"{self.project.slug},foo")) > 0
  258. # checks that second transaction wasn't sent to the trends microservice
  259. assert len(trends_call_args_data.get(f"{self.project.slug},bar", [])) == 0