test_organization_events_facets_performance.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from datetime import timedelta
  2. from django.urls import reverse
  3. from sentry.testutils.cases import APITestCase, SnubaTestCase
  4. from sentry.testutils.helpers.datetime import before_now, iso_format
  5. from sentry.testutils.silo import region_silo_test
  6. from sentry.utils.samples import load_data
  7. class BaseOrganizationEventsFacetsPerformanceEndpointTest(SnubaTestCase, APITestCase):
  8. url: str
  9. feature_list = (
  10. "organizations:discover-basic",
  11. "organizations:global-views",
  12. "organizations:performance-view",
  13. )
  14. def setUp(self):
  15. super().setUp()
  16. self.min_ago = before_now(minutes=1).replace(microsecond=0)
  17. self.two_mins_ago = before_now(minutes=2).replace(microsecond=0)
  18. self.day_ago = before_now(days=1).replace(microsecond=0)
  19. self.login_as(user=self.user)
  20. self.project = self.create_project()
  21. self.project2 = self.create_project()
  22. def do_request(self, query=None, features=None):
  23. query = query if query is not None else {"aggregateColumn": "transaction.duration"}
  24. query["project"] = query["project"] if "project" in query else [self.project.id]
  25. feature_dict = {feature: True for feature in self.feature_list}
  26. feature_dict.update(features or {})
  27. with self.feature(feature_dict):
  28. return self.client.get(self.url, query, format="json")
  29. @region_silo_test
  30. class OrganizationEventsFacetsPerformanceEndpointTest(
  31. BaseOrganizationEventsFacetsPerformanceEndpointTest
  32. ):
  33. def setUp(self):
  34. super().setUp()
  35. self._transaction_count = 0
  36. for i in range(5):
  37. self.store_transaction(
  38. tags=[["color", "blue"], ["many", "yes"]], duration=4000, lcp=3000
  39. )
  40. for i in range(14):
  41. self.store_transaction(tags=[["color", "red"], ["many", "yes"]], duration=1000, lcp=500)
  42. for i in range(1):
  43. self.store_transaction(
  44. tags=[["color", "green"], ["many", "no"]], duration=5000, lcp=4000
  45. )
  46. self.url = reverse(
  47. "sentry-api-0-organization-events-facets-performance",
  48. kwargs={"organization_slug": self.project.organization.slug},
  49. )
  50. def store_transaction(
  51. self, name="exampleTransaction", duration=100, tags=None, project_id=None, lcp=None
  52. ):
  53. if tags is None:
  54. tags = []
  55. if project_id is None:
  56. project_id = self.project.id
  57. event = load_data("transaction").copy()
  58. event.data["tags"].extend(tags)
  59. event.update(
  60. {
  61. "transaction": name,
  62. "event_id": f"{self._transaction_count:02x}".rjust(32, "0"),
  63. "start_timestamp": iso_format(self.two_mins_ago - timedelta(seconds=duration)),
  64. "timestamp": iso_format(self.two_mins_ago),
  65. }
  66. )
  67. if lcp:
  68. event["measurements"]["lcp"]["value"] = lcp
  69. else:
  70. del event["measurements"]["lcp"]
  71. self._transaction_count += 1
  72. self.store_event(data=event, project_id=project_id)
  73. def test_basic_request(self):
  74. response = self.do_request()
  75. assert response.status_code == 200, response.content
  76. data = response.data["data"]
  77. assert len(data) == 2
  78. assert data[0] == {
  79. "aggregate": 4000000.0,
  80. "comparison": 2.051282051282051,
  81. "count": 5,
  82. "frequency": 0.25,
  83. "sumdelta": 10250000.0,
  84. "tags_key": "color",
  85. "tags_value": "blue",
  86. }
  87. def test_sort_frequency(self):
  88. # Descending
  89. response = self.do_request(
  90. {
  91. "aggregateColumn": "transaction.duration",
  92. "sort": "-frequency",
  93. "per_page": 20,
  94. "statsPeriod": "14d",
  95. }
  96. )
  97. assert response.status_code == 200, response.content
  98. data = response.data["data"]
  99. assert len(data) == 2
  100. # The first set of generated is the most frequent since the 14 transactions are excluded because of 1000 duration
  101. assert data[0]["count"] == 5
  102. assert data[0]["tags_key"] == "color"
  103. assert data[0]["tags_value"] == "blue"
  104. # The 14 transactions with many=yes are excluded because of 1000 duration
  105. assert data[1]["count"] == 1
  106. assert data[1]["tags_key"] == "many"
  107. assert data[1]["tags_value"] == "no"
  108. # Ascending
  109. response = self.do_request(
  110. {
  111. "aggregateColumn": "transaction.duration",
  112. "sort": "frequency",
  113. "per_page": 5,
  114. "statsPeriod": "14d",
  115. }
  116. )
  117. data = response.data["data"]
  118. assert len(data) == 2
  119. assert data[0]["count"] == 1
  120. assert data[0]["tags_key"] == "color"
  121. assert data[0]["tags_value"] == "green"
  122. assert data[1]["count"] == 1
  123. assert data[1]["tags_key"] == "many"
  124. assert data[1]["tags_value"] == "no"
  125. def test_basic_query(self):
  126. response = self.do_request(
  127. {
  128. "aggregateColumn": "transaction.duration",
  129. "sort": "-frequency",
  130. "per_page": 5,
  131. "statsPeriod": "14d",
  132. "query": "(color:red or color:blue)",
  133. }
  134. )
  135. assert response.status_code == 200, response.content
  136. data = response.data["data"]
  137. assert len(data) == 1
  138. assert data[0]["count"] == 5
  139. assert data[0]["tags_key"] == "color"
  140. assert data[0]["tags_value"] == "blue"
  141. def test_multiple_projects_not_allowed(self):
  142. response = self.do_request(
  143. {
  144. "aggregateColumn": "transaction.duration",
  145. "project": [self.project.id, self.project2.id],
  146. }
  147. )
  148. assert response.status_code == 400, response.content
  149. assert response.data == {
  150. "detail": "You cannot view facet performance for multiple projects."
  151. }
  152. def test_missing_tags_column(self):
  153. response = self.do_request({})
  154. assert response.status_code == 400, response.content
  155. assert response.data == {"detail": "'aggregateColumn' must be provided."}
  156. def test_invalid_tags_column(self):
  157. response = self.do_request({"aggregateColumn": "abc"})
  158. assert response.status_code == 400, response.content
  159. assert response.data == {"detail": "'abc' is not a supported tags column."}
  160. def test_all_tag_keys(self):
  161. request = {
  162. "aggregateColumn": "transaction.duration",
  163. "sort": "-frequency",
  164. "per_page": 5,
  165. "statsPeriod": "14d",
  166. "query": "(color:red or color:blue)",
  167. "allTagKeys": True,
  168. }
  169. # With feature access
  170. response = self.do_request(request)
  171. assert response.status_code == 200, response.content
  172. data = response.data["data"]
  173. assert len(data) == 5
  174. assert data[0]["count"] == 19
  175. assert data[0]["tags_key"] == "application"
  176. assert data[0]["tags_value"] == "countries"
  177. def test_tag_frequency(self):
  178. # LCP-less transaction should be ignored in total counts for frequency.
  179. self.store_transaction(tags=[["color", "orange"], ["many", "maybe"]], lcp=None)
  180. request = {
  181. "aggregateColumn": "measurements.lcp",
  182. "sort": "-frequency",
  183. "per_page": 5,
  184. "statsPeriod": "14d",
  185. "query": "(color:red or color:blue)",
  186. "allTagKeys": True,
  187. }
  188. response = self.do_request(request)
  189. assert response.status_code == 200, response.content
  190. data = response.data["data"]
  191. assert len(data) == 5
  192. assert data[0]["count"] == 19
  193. assert data[0]["tags_key"] == "application"
  194. assert data[0]["tags_value"] == "countries"
  195. # Only transactions with lcp should be considered
  196. assert data[0]["frequency"] == 1
  197. def test_tag_key_values(self):
  198. request = {
  199. "aggregateColumn": "transaction.duration",
  200. "sort": "-frequency",
  201. "per_page": 5,
  202. "statsPeriod": "14d",
  203. "tagKey": "color",
  204. }
  205. # With feature access
  206. response = self.do_request(request)
  207. assert response.status_code == 200, response.content
  208. data = response.data["data"]
  209. assert len(data) == 3
  210. assert data[0]["count"] == 14
  211. assert data[0]["tags_key"] == "color"
  212. assert data[0]["tags_value"] == "red"
  213. def test_aggregate_zero(self):
  214. # LCP-less transaction should be ignored in total counts for frequency.
  215. self.store_transaction(tags=[["color", "purple"]], duration=0)
  216. request = {
  217. "aggregateColumn": "transaction.duration",
  218. "sort": "-frequency",
  219. "per_page": 5,
  220. "statsPeriod": "14d",
  221. "tagKey": "color",
  222. "query": "(color:purple)",
  223. }
  224. response = self.do_request(request)
  225. assert response.status_code == 200, response.content
  226. data = response.data["data"]
  227. assert len(data) == 1
  228. assert data[0]["count"] == 1
  229. assert data[0]["comparison"] == 0
  230. assert data[0]["tags_key"] == "color"
  231. assert data[0]["tags_value"] == "purple"