test_organization_events_mep.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071
  1. from unittest import mock
  2. from django.urls import reverse
  3. from snuba_sdk.conditions import InvalidConditionError
  4. from sentry.discover.models import TeamKeyTransaction
  5. from sentry.exceptions import IncompatibleMetricsQuery, InvalidSearchQuery
  6. from sentry.models import ProjectTeam
  7. from sentry.search.events import constants
  8. from sentry.testutils import MetricsEnhancedPerformanceTestCase
  9. from sentry.testutils.helpers.datetime import before_now, iso_format
  10. from sentry.utils.samples import load_data
  11. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  12. viewname = "sentry-api-0-organization-events"
  13. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  14. METRIC_STRINGS = [
  15. "foo_transaction",
  16. "bar_transaction",
  17. "baz_transaction",
  18. "staging",
  19. "measurement_rating",
  20. "good",
  21. "meh",
  22. "d:transactions/measurements.something_custom@millisecond",
  23. ]
  24. def setUp(self):
  25. super().setUp()
  26. self.min_ago = before_now(minutes=1)
  27. self.two_min_ago = before_now(minutes=2)
  28. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  29. self.features = {
  30. "organizations:performance-use-metrics": True,
  31. }
  32. def do_request(self, query, features=None):
  33. if features is None:
  34. features = {"organizations:discover-basic": True}
  35. features.update(self.features)
  36. self.login_as(user=self.user)
  37. url = reverse(
  38. self.viewname,
  39. kwargs={"organization_slug": self.organization.slug},
  40. )
  41. with self.feature(features):
  42. return self.client.get(url, query, format="json")
  43. def test_no_projects(self):
  44. response = self.do_request(
  45. {
  46. "dataset": "metricsEnhanced",
  47. }
  48. )
  49. assert response.status_code == 200, response.content
  50. def test_invalid_dataset(self):
  51. response = self.do_request(
  52. {
  53. "dataset": "aFakeDataset",
  54. "project": self.project.id,
  55. }
  56. )
  57. assert response.status_code == 400, response.content
  58. assert (
  59. response.data["detail"] == "dataset must be one of: discover, metricsEnhanced, metrics"
  60. )
  61. def test_out_of_retention(self):
  62. self.create_project()
  63. with self.options({"system.event-retention-days": 10}):
  64. query = {
  65. "field": ["id", "timestamp"],
  66. "orderby": ["-timestamp", "-id"],
  67. "query": "event.type:transaction",
  68. "start": iso_format(before_now(days=20)),
  69. "end": iso_format(before_now(days=15)),
  70. "dataset": "metricsEnhanced",
  71. }
  72. response = self.do_request(query)
  73. assert response.status_code == 400, response.content
  74. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  75. def test_invalid_search_terms(self):
  76. response = self.do_request(
  77. {
  78. "field": ["epm()"],
  79. "query": "hi \n there",
  80. "project": self.project.id,
  81. "dataset": "metricsEnhanced",
  82. }
  83. )
  84. assert response.status_code == 400, response.content
  85. assert (
  86. response.data["detail"]
  87. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  88. )
  89. def test_percentile_with_no_data(self):
  90. response = self.do_request(
  91. {
  92. "field": ["p50()"],
  93. "query": "",
  94. "project": self.project.id,
  95. "dataset": "metricsEnhanced",
  96. }
  97. )
  98. assert response.status_code == 200, response.content
  99. data = response.data["data"]
  100. assert len(data) == 1
  101. assert data[0]["p50()"] == 0
  102. def test_project_name(self):
  103. self.store_metric(
  104. 1,
  105. tags={"environment": "staging"},
  106. timestamp=self.min_ago,
  107. )
  108. response = self.do_request(
  109. {
  110. "field": ["project.name", "environment", "epm()"],
  111. "query": "event.type:transaction",
  112. "dataset": "metricsEnhanced",
  113. "per_page": 50,
  114. }
  115. )
  116. assert response.status_code == 200, response.content
  117. assert len(response.data["data"]) == 1
  118. data = response.data["data"]
  119. meta = response.data["meta"]
  120. field_meta = meta["fields"]
  121. assert data[0]["project.name"] == self.project.slug
  122. assert "project.id" not in data[0]
  123. assert data[0]["environment"] == "staging"
  124. assert meta["isMetricsData"]
  125. assert field_meta["project.name"] == "string"
  126. assert field_meta["environment"] == "string"
  127. assert field_meta["epm()"] == "number"
  128. def test_title_alias(self):
  129. """title is an alias to transaction name"""
  130. self.store_metric(
  131. 1,
  132. tags={"transaction": "foo_transaction"},
  133. timestamp=self.min_ago,
  134. )
  135. response = self.do_request(
  136. {
  137. "field": ["title", "p50()"],
  138. "query": "event.type:transaction",
  139. "dataset": "metricsEnhanced",
  140. "per_page": 50,
  141. }
  142. )
  143. assert response.status_code == 200, response.content
  144. assert len(response.data["data"]) == 1
  145. data = response.data["data"]
  146. meta = response.data["meta"]
  147. field_meta = meta["fields"]
  148. assert data[0]["title"] == "foo_transaction"
  149. assert data[0]["p50()"] == 1
  150. assert meta["isMetricsData"]
  151. assert field_meta["title"] == "string"
  152. assert field_meta["p50()"] == "duration"
  153. def test_having_condition(self):
  154. self.store_metric(
  155. 1,
  156. tags={"environment": "staging", "transaction": "foo_transaction"},
  157. timestamp=self.min_ago,
  158. )
  159. self.store_metric(
  160. # shouldn't show up
  161. 100,
  162. tags={"environment": "staging", "transaction": "bar_transaction"},
  163. timestamp=self.min_ago,
  164. )
  165. response = self.do_request(
  166. {
  167. "field": ["transaction", "project", "p50(transaction.duration)"],
  168. "query": "event.type:transaction p50(transaction.duration):<50",
  169. "dataset": "metricsEnhanced",
  170. "per_page": 50,
  171. }
  172. )
  173. assert response.status_code == 200, response.content
  174. assert len(response.data["data"]) == 1
  175. data = response.data["data"]
  176. meta = response.data["meta"]
  177. field_meta = meta["fields"]
  178. assert data[0]["transaction"] == "foo_transaction"
  179. assert data[0]["project"] == self.project.slug
  180. assert data[0]["p50(transaction.duration)"] == 1
  181. assert meta["isMetricsData"]
  182. assert field_meta["transaction"] == "string"
  183. assert field_meta["project"] == "string"
  184. assert field_meta["p50(transaction.duration)"] == "duration"
  185. def test_having_condition_with_preventing_aggregates(self):
  186. self.store_metric(
  187. 1,
  188. tags={"environment": "staging", "transaction": "foo_transaction"},
  189. timestamp=self.min_ago,
  190. )
  191. self.store_metric(
  192. 100,
  193. tags={"environment": "staging", "transaction": "bar_transaction"},
  194. timestamp=self.min_ago,
  195. )
  196. response = self.do_request(
  197. {
  198. "field": ["transaction", "project", "p50(transaction.duration)"],
  199. "query": "event.type:transaction p50(transaction.duration):<50",
  200. "dataset": "metricsEnhanced",
  201. "preventMetricAggregates": "1",
  202. "per_page": 50,
  203. }
  204. )
  205. assert response.status_code == 200, response.content
  206. assert len(response.data["data"]) == 0
  207. meta = response.data["meta"]
  208. field_meta = meta["fields"]
  209. assert not meta["isMetricsData"]
  210. assert field_meta["transaction"] == "string"
  211. assert field_meta["project"] == "string"
  212. assert field_meta["p50(transaction.duration)"] == "duration"
  213. def test_having_condition_with_preventing_aggregate_metrics_only(self):
  214. """same as the previous test, but with the dataset on explicit metrics
  215. which should throw a 400 error instead"""
  216. response = self.do_request(
  217. {
  218. "field": ["transaction", "project", "p50(transaction.duration)"],
  219. "query": "event.type:transaction p50(transaction.duration):<50",
  220. "dataset": "metrics",
  221. "preventMetricAggregates": "1",
  222. "per_page": 50,
  223. "project": self.project.id,
  224. }
  225. )
  226. assert response.status_code == 400, response.content
  227. def test_having_condition_not_selected(self):
  228. self.store_metric(
  229. 1,
  230. tags={"environment": "staging", "transaction": "foo_transaction"},
  231. timestamp=self.min_ago,
  232. )
  233. self.store_metric(
  234. # shouldn't show up
  235. 100,
  236. tags={"environment": "staging", "transaction": "bar_transaction"},
  237. timestamp=self.min_ago,
  238. )
  239. response = self.do_request(
  240. {
  241. "field": ["transaction", "project", "p50(transaction.duration)"],
  242. "query": "event.type:transaction p75(transaction.duration):<50",
  243. "dataset": "metricsEnhanced",
  244. "per_page": 50,
  245. }
  246. )
  247. assert response.status_code == 200, response.content
  248. assert len(response.data["data"]) == 1
  249. data = response.data["data"]
  250. meta = response.data["meta"]
  251. field_meta = meta["fields"]
  252. assert data[0]["transaction"] == "foo_transaction"
  253. assert data[0]["project"] == self.project.slug
  254. assert data[0]["p50(transaction.duration)"] == 1
  255. assert meta["isMetricsData"]
  256. assert field_meta["transaction"] == "string"
  257. assert field_meta["project"] == "string"
  258. assert field_meta["p50(transaction.duration)"] == "duration"
  259. def test_non_metrics_tag_with_implicit_format(self):
  260. self.store_metric(
  261. 1,
  262. tags={"environment": "staging", "transaction": "foo_transaction"},
  263. timestamp=self.min_ago,
  264. )
  265. response = self.do_request(
  266. {
  267. "field": ["test", "p50(transaction.duration)"],
  268. "query": "event.type:transaction",
  269. "dataset": "metricsEnhanced",
  270. "per_page": 50,
  271. }
  272. )
  273. assert response.status_code == 200, response.content
  274. assert len(response.data["data"]) == 0
  275. assert not response.data["meta"]["isMetricsData"]
  276. def test_non_metrics_tag_with_implicit_format_metrics_dataset(self):
  277. self.store_metric(
  278. 1,
  279. tags={"environment": "staging", "transaction": "foo_transaction"},
  280. timestamp=self.min_ago,
  281. )
  282. response = self.do_request(
  283. {
  284. "field": ["test", "p50(transaction.duration)"],
  285. "query": "event.type:transaction",
  286. "dataset": "metrics",
  287. "per_page": 50,
  288. }
  289. )
  290. assert response.status_code == 400, response.content
  291. def test_performance_homepage_query(self):
  292. self.store_metric(
  293. 1,
  294. tags={
  295. "transaction": "foo_transaction",
  296. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  297. },
  298. timestamp=self.min_ago,
  299. )
  300. self.store_metric(
  301. 1,
  302. "measurements.fcp",
  303. tags={"transaction": "foo_transaction"},
  304. timestamp=self.min_ago,
  305. )
  306. self.store_metric(
  307. 2,
  308. "measurements.lcp",
  309. tags={"transaction": "foo_transaction"},
  310. timestamp=self.min_ago,
  311. )
  312. self.store_metric(
  313. 3,
  314. "measurements.fid",
  315. tags={"transaction": "foo_transaction"},
  316. timestamp=self.min_ago,
  317. )
  318. self.store_metric(
  319. 4,
  320. "measurements.cls",
  321. tags={"transaction": "foo_transaction"},
  322. timestamp=self.min_ago,
  323. )
  324. self.store_metric(
  325. 1,
  326. "user",
  327. tags={
  328. "transaction": "foo_transaction",
  329. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
  330. },
  331. timestamp=self.min_ago,
  332. )
  333. for dataset in ["metrics", "metricsEnhanced"]:
  334. response = self.do_request(
  335. {
  336. "field": [
  337. "transaction",
  338. "project",
  339. "tpm()",
  340. "p75(measurements.fcp)",
  341. "p75(measurements.lcp)",
  342. "p75(measurements.fid)",
  343. "p75(measurements.cls)",
  344. "count_unique(user)",
  345. "apdex()",
  346. "count_miserable(user)",
  347. "user_misery()",
  348. ],
  349. "query": "event.type:transaction",
  350. "dataset": dataset,
  351. "per_page": 50,
  352. }
  353. )
  354. assert len(response.data["data"]) == 1
  355. data = response.data["data"][0]
  356. meta = response.data["meta"]
  357. field_meta = meta["fields"]
  358. assert data["transaction"] == "foo_transaction"
  359. assert data["project"] == self.project.slug
  360. assert data["p75(measurements.fcp)"] == 1.0
  361. assert data["p75(measurements.lcp)"] == 2.0
  362. assert data["p75(measurements.fid)"] == 3.0
  363. assert data["p75(measurements.cls)"] == 4.0
  364. assert data["apdex()"] == 1.0
  365. assert data["count_miserable(user)"] == 1.0
  366. assert data["user_misery()"] == 0.058
  367. assert meta["isMetricsData"]
  368. assert field_meta["transaction"] == "string"
  369. assert field_meta["project"] == "string"
  370. assert field_meta["p75(measurements.fcp)"] == "duration"
  371. assert field_meta["p75(measurements.lcp)"] == "duration"
  372. assert field_meta["p75(measurements.fid)"] == "duration"
  373. assert field_meta["p75(measurements.cls)"] == "duration"
  374. assert field_meta["apdex()"] == "number"
  375. assert field_meta["count_miserable(user)"] == "integer"
  376. assert field_meta["user_misery()"] == "number"
  377. def test_no_team_key_transactions(self):
  378. self.store_metric(1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago)
  379. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  380. query = {
  381. "team": "myteams",
  382. "project": [self.project.id],
  383. # TODO sort by transaction here once that's possible for order to match the same test without metrics
  384. "orderby": "p95()",
  385. "field": [
  386. "team_key_transaction",
  387. "transaction",
  388. "transaction.status",
  389. "project",
  390. "epm()",
  391. "failure_rate()",
  392. "p95()",
  393. ],
  394. "per_page": 50,
  395. "dataset": "metricsEnhanced",
  396. }
  397. response = self.do_request(query)
  398. assert response.status_code == 200, response.content
  399. assert len(response.data["data"]) == 2
  400. data = response.data["data"]
  401. meta = response.data["meta"]
  402. field_meta = meta["fields"]
  403. assert data[0]["team_key_transaction"] == 0
  404. assert data[0]["transaction"] == "foo_transaction"
  405. assert data[1]["team_key_transaction"] == 0
  406. assert data[1]["transaction"] == "bar_transaction"
  407. assert meta["isMetricsData"]
  408. assert field_meta["team_key_transaction"] == "boolean"
  409. assert field_meta["transaction"] == "string"
  410. def test_team_key_transactions_my_teams(self):
  411. team1 = self.create_team(organization=self.organization, name="Team A")
  412. self.create_team_membership(team1, user=self.user)
  413. self.project.add_team(team1)
  414. team2 = self.create_team(organization=self.organization, name="Team B")
  415. self.project.add_team(team2)
  416. key_transactions = [
  417. (team1, "foo_transaction"),
  418. (team2, "baz_transaction"),
  419. ]
  420. # Not a key transaction
  421. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  422. for team, transaction in key_transactions:
  423. self.store_metric(1, tags={"transaction": transaction}, timestamp=self.min_ago)
  424. TeamKeyTransaction.objects.create(
  425. organization=self.organization,
  426. transaction=transaction,
  427. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  428. )
  429. query = {
  430. "team": "myteams",
  431. "project": [self.project.id],
  432. "field": [
  433. "team_key_transaction",
  434. "transaction",
  435. "transaction.status",
  436. "project",
  437. "epm()",
  438. "failure_rate()",
  439. "p95()",
  440. ],
  441. "per_page": 50,
  442. "dataset": "metricsEnhanced",
  443. }
  444. query["orderby"] = ["team_key_transaction", "p95()"]
  445. response = self.do_request(query)
  446. assert response.status_code == 200, response.content
  447. assert len(response.data["data"]) == 3
  448. data = response.data["data"]
  449. meta = response.data["meta"]
  450. field_meta = meta["fields"]
  451. assert data[0]["team_key_transaction"] == 0
  452. assert data[0]["transaction"] == "baz_transaction"
  453. assert data[1]["team_key_transaction"] == 0
  454. assert data[1]["transaction"] == "bar_transaction"
  455. assert data[2]["team_key_transaction"] == 1
  456. assert data[2]["transaction"] == "foo_transaction"
  457. assert meta["isMetricsData"]
  458. assert field_meta["team_key_transaction"] == "boolean"
  459. assert field_meta["transaction"] == "string"
  460. # not specifying any teams should use my teams
  461. query = {
  462. "project": [self.project.id],
  463. "field": [
  464. "team_key_transaction",
  465. "transaction",
  466. "transaction.status",
  467. "project",
  468. "epm()",
  469. "failure_rate()",
  470. "p95()",
  471. ],
  472. "per_page": 50,
  473. "dataset": "metricsEnhanced",
  474. }
  475. query["orderby"] = ["team_key_transaction", "p95()"]
  476. response = self.do_request(query)
  477. assert response.status_code == 200, response.content
  478. assert len(response.data["data"]) == 3
  479. data = response.data["data"]
  480. meta = response.data["meta"]
  481. field_meta = meta["fields"]
  482. assert data[0]["team_key_transaction"] == 0
  483. assert data[0]["transaction"] == "baz_transaction"
  484. assert data[1]["team_key_transaction"] == 0
  485. assert data[1]["transaction"] == "bar_transaction"
  486. assert data[2]["team_key_transaction"] == 1
  487. assert data[2]["transaction"] == "foo_transaction"
  488. assert meta["isMetricsData"]
  489. assert field_meta["team_key_transaction"] == "boolean"
  490. assert field_meta["transaction"] == "string"
  491. def test_team_key_transactions_orderby(self):
  492. team1 = self.create_team(organization=self.organization, name="Team A")
  493. team2 = self.create_team(organization=self.organization, name="Team B")
  494. key_transactions = [
  495. (team1, "foo_transaction", 1),
  496. (team2, "baz_transaction", 100),
  497. ]
  498. # Not a key transaction
  499. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  500. for team, transaction, value in key_transactions:
  501. self.store_metric(value, tags={"transaction": transaction}, timestamp=self.min_ago)
  502. self.create_team_membership(team, user=self.user)
  503. self.project.add_team(team)
  504. TeamKeyTransaction.objects.create(
  505. organization=self.organization,
  506. transaction=transaction,
  507. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  508. )
  509. query = {
  510. "team": "myteams",
  511. "project": [self.project.id],
  512. "field": [
  513. "team_key_transaction",
  514. "transaction",
  515. "transaction.status",
  516. "project",
  517. "epm()",
  518. "failure_rate()",
  519. "p95()",
  520. ],
  521. "per_page": 50,
  522. "dataset": "metricsEnhanced",
  523. }
  524. # test ascending order
  525. query["orderby"] = ["team_key_transaction", "p95()"]
  526. response = self.do_request(query)
  527. assert response.status_code == 200, response.content
  528. assert len(response.data["data"]) == 3
  529. data = response.data["data"]
  530. meta = response.data["meta"]
  531. field_meta = meta["fields"]
  532. assert data[0]["team_key_transaction"] == 0
  533. assert data[0]["transaction"] == "bar_transaction"
  534. assert data[1]["team_key_transaction"] == 1
  535. assert data[1]["transaction"] == "foo_transaction"
  536. assert data[2]["team_key_transaction"] == 1
  537. assert data[2]["transaction"] == "baz_transaction"
  538. assert meta["isMetricsData"]
  539. assert field_meta["team_key_transaction"] == "boolean"
  540. assert field_meta["transaction"] == "string"
  541. # test descending order
  542. query["orderby"] = ["-team_key_transaction", "p95()"]
  543. response = self.do_request(query)
  544. assert response.status_code == 200, response.content
  545. assert len(response.data["data"]) == 3
  546. data = response.data["data"]
  547. meta = response.data["meta"]
  548. field_meta = meta["fields"]
  549. assert data[0]["team_key_transaction"] == 1
  550. assert data[0]["transaction"] == "foo_transaction"
  551. assert data[1]["team_key_transaction"] == 1
  552. assert data[1]["transaction"] == "baz_transaction"
  553. assert data[2]["team_key_transaction"] == 0
  554. assert data[2]["transaction"] == "bar_transaction"
  555. assert meta["isMetricsData"]
  556. assert field_meta["team_key_transaction"] == "boolean"
  557. assert field_meta["transaction"] == "string"
  558. def test_team_key_transactions_query(self):
  559. team1 = self.create_team(organization=self.organization, name="Team A")
  560. team2 = self.create_team(organization=self.organization, name="Team B")
  561. key_transactions = [
  562. (team1, "foo_transaction", 1),
  563. (team2, "baz_transaction", 100),
  564. ]
  565. # Not a key transaction
  566. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  567. for team, transaction, value in key_transactions:
  568. self.store_metric(value, tags={"transaction": transaction}, timestamp=self.min_ago)
  569. self.create_team_membership(team, user=self.user)
  570. self.project.add_team(team)
  571. TeamKeyTransaction.objects.create(
  572. organization=self.organization,
  573. transaction=transaction,
  574. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  575. )
  576. query = {
  577. "team": "myteams",
  578. "project": [self.project.id],
  579. # use the order by to ensure the result order
  580. "orderby": "p95()",
  581. "field": [
  582. "team_key_transaction",
  583. "transaction",
  584. "transaction.status",
  585. "project",
  586. "epm()",
  587. "failure_rate()",
  588. "p95()",
  589. ],
  590. "per_page": 50,
  591. "dataset": "metricsEnhanced",
  592. }
  593. # key transactions
  594. query["query"] = "has:team_key_transaction"
  595. response = self.do_request(query)
  596. assert response.status_code == 200, response.content
  597. assert len(response.data["data"]) == 2
  598. data = response.data["data"]
  599. meta = response.data["meta"]
  600. field_meta = meta["fields"]
  601. assert data[0]["team_key_transaction"] == 1
  602. assert data[0]["transaction"] == "foo_transaction"
  603. assert data[1]["team_key_transaction"] == 1
  604. assert data[1]["transaction"] == "baz_transaction"
  605. assert meta["isMetricsData"]
  606. assert field_meta["team_key_transaction"] == "boolean"
  607. assert field_meta["transaction"] == "string"
  608. # key transactions
  609. query["query"] = "team_key_transaction:true"
  610. response = self.do_request(query)
  611. assert response.status_code == 200, response.content
  612. assert len(response.data["data"]) == 2
  613. data = response.data["data"]
  614. meta = response.data["meta"]
  615. field_meta = meta["fields"]
  616. assert data[0]["team_key_transaction"] == 1
  617. assert data[0]["transaction"] == "foo_transaction"
  618. assert data[1]["team_key_transaction"] == 1
  619. assert data[1]["transaction"] == "baz_transaction"
  620. assert meta["isMetricsData"]
  621. assert field_meta["team_key_transaction"] == "boolean"
  622. assert field_meta["transaction"] == "string"
  623. # not key transactions
  624. query["query"] = "!has:team_key_transaction"
  625. response = self.do_request(query)
  626. assert response.status_code == 200, response.content
  627. assert len(response.data["data"]) == 1
  628. data = response.data["data"]
  629. meta = response.data["meta"]
  630. field_meta = meta["fields"]
  631. assert data[0]["team_key_transaction"] == 0
  632. assert data[0]["transaction"] == "bar_transaction"
  633. assert meta["isMetricsData"]
  634. assert field_meta["team_key_transaction"] == "boolean"
  635. assert field_meta["transaction"] == "string"
  636. # not key transactions
  637. query["query"] = "team_key_transaction:false"
  638. response = self.do_request(query)
  639. assert response.status_code == 200, response.content
  640. assert len(response.data["data"]) == 1
  641. data = response.data["data"]
  642. meta = response.data["meta"]
  643. field_meta = meta["fields"]
  644. assert data[0]["team_key_transaction"] == 0
  645. assert data[0]["transaction"] == "bar_transaction"
  646. assert meta["isMetricsData"]
  647. assert field_meta["team_key_transaction"] == "boolean"
  648. assert field_meta["transaction"] == "string"
  649. def test_too_many_team_key_transactions(self):
  650. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  651. with mock.patch(
  652. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  653. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  654. ):
  655. team = self.create_team(organization=self.organization, name="Team A")
  656. self.create_team_membership(team, user=self.user)
  657. self.project.add_team(team)
  658. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  659. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  660. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  661. self.store_metric(
  662. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  663. )
  664. TeamKeyTransaction.objects.bulk_create(
  665. [
  666. TeamKeyTransaction(
  667. organization=self.organization,
  668. project_team=project_team,
  669. transaction=transactions[i],
  670. )
  671. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  672. ]
  673. )
  674. query = {
  675. "team": "myteams",
  676. "project": [self.project.id],
  677. "orderby": "p95()",
  678. "field": [
  679. "team_key_transaction",
  680. "transaction",
  681. "transaction.status",
  682. "project",
  683. "epm()",
  684. "failure_rate()",
  685. "p95()",
  686. ],
  687. "dataset": "metricsEnhanced",
  688. "per_page": 50,
  689. }
  690. response = self.do_request(query)
  691. assert response.status_code == 200, response.content
  692. assert len(response.data["data"]) == 2
  693. data = response.data["data"]
  694. meta = response.data["meta"]
  695. assert (
  696. sum(row["team_key_transaction"] for row in data)
  697. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  698. )
  699. assert meta["isMetricsData"]
  700. def test_measurement_rating(self):
  701. self.store_metric(
  702. 50,
  703. metric="measurements.lcp",
  704. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  705. timestamp=self.min_ago,
  706. )
  707. self.store_metric(
  708. 15,
  709. metric="measurements.fp",
  710. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  711. timestamp=self.min_ago,
  712. )
  713. self.store_metric(
  714. 1500,
  715. metric="measurements.fcp",
  716. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  717. timestamp=self.min_ago,
  718. )
  719. self.store_metric(
  720. 125,
  721. metric="measurements.fid",
  722. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  723. timestamp=self.min_ago,
  724. )
  725. self.store_metric(
  726. 0.15,
  727. metric="measurements.cls",
  728. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  729. timestamp=self.min_ago,
  730. )
  731. response = self.do_request(
  732. {
  733. "field": [
  734. "transaction",
  735. "count_web_vitals(measurements.lcp, good)",
  736. "count_web_vitals(measurements.fp, good)",
  737. "count_web_vitals(measurements.fcp, meh)",
  738. "count_web_vitals(measurements.fid, meh)",
  739. "count_web_vitals(measurements.cls, good)",
  740. ],
  741. "query": "event.type:transaction",
  742. "dataset": "metricsEnhanced",
  743. "per_page": 50,
  744. }
  745. )
  746. assert response.status_code == 200, response.content
  747. assert len(response.data["data"]) == 1
  748. data = response.data["data"]
  749. meta = response.data["meta"]
  750. field_meta = meta["fields"]
  751. assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
  752. assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
  753. assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
  754. assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
  755. assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
  756. assert meta["isMetricsData"]
  757. assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
  758. assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
  759. assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
  760. assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
  761. assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
  762. def test_measurement_rating_that_does_not_exist(self):
  763. self.store_metric(
  764. 1,
  765. metric="measurements.lcp",
  766. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  767. timestamp=self.min_ago,
  768. )
  769. response = self.do_request(
  770. {
  771. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  772. "query": "event.type:transaction",
  773. "dataset": "metricsEnhanced",
  774. "per_page": 50,
  775. }
  776. )
  777. assert response.status_code == 200, response.content
  778. assert len(response.data["data"]) == 1
  779. data = response.data["data"]
  780. meta = response.data["meta"]
  781. assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
  782. assert meta["isMetricsData"]
  783. assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
  784. def test_count_web_vitals_invalid_vital(self):
  785. query = {
  786. "field": [
  787. "count_web_vitals(measurements.foo, poor)",
  788. ],
  789. "project": [self.project.id],
  790. "dataset": "metricsEnhanced",
  791. }
  792. response = self.do_request(query)
  793. assert response.status_code == 400, response.content
  794. query = {
  795. "field": [
  796. "count_web_vitals(tags[lcp], poor)",
  797. ],
  798. "project": [self.project.id],
  799. "dataset": "metricsEnhanced",
  800. }
  801. response = self.do_request(query)
  802. assert response.status_code == 400, response.content
  803. query = {
  804. "field": [
  805. "count_web_vitals(transaction.duration, poor)",
  806. ],
  807. "project": [self.project.id],
  808. "dataset": "metricsEnhanced",
  809. }
  810. response = self.do_request(query)
  811. assert response.status_code == 400, response.content
  812. query = {
  813. "field": [
  814. "count_web_vitals(measurements.lcp, bad)",
  815. ],
  816. "project": [self.project.id],
  817. "dataset": "metricsEnhanced",
  818. }
  819. response = self.do_request(query)
  820. assert response.status_code == 400, response.content
  821. @mock.patch("sentry.snuba.metrics_performance.MetricsQueryBuilder")
  822. def test_failed_dry_run_does_not_error(self, mock_builder):
  823. with self.feature("organizations:performance-dry-run-mep"):
  824. mock_builder.side_effect = InvalidSearchQuery("Something bad")
  825. query = {
  826. "field": ["count()"],
  827. "project": [self.project.id],
  828. }
  829. response = self.do_request(query)
  830. assert response.status_code == 200, response.content
  831. assert len(mock_builder.mock_calls) == 1
  832. assert mock_builder.call_args.kwargs["dry_run"]
  833. mock_builder.side_effect = IncompatibleMetricsQuery("Something bad")
  834. query = {
  835. "field": ["count()"],
  836. "project": [self.project.id],
  837. }
  838. response = self.do_request(query)
  839. assert response.status_code == 200, response.content
  840. assert len(mock_builder.mock_calls) == 2
  841. assert mock_builder.call_args.kwargs["dry_run"]
  842. mock_builder.side_effect = InvalidConditionError("Something bad")
  843. query = {
  844. "field": ["count()"],
  845. "project": [self.project.id],
  846. }
  847. response = self.do_request(query)
  848. assert response.status_code == 200, response.content
  849. assert len(mock_builder.mock_calls) == 3
  850. assert mock_builder.call_args.kwargs["dry_run"]
  851. def test_count_unique_user_returns_zero(self):
  852. self.store_metric(
  853. 50,
  854. metric="user",
  855. tags={"transaction": "foo_transaction"},
  856. timestamp=self.min_ago,
  857. )
  858. self.store_metric(
  859. 50,
  860. tags={"transaction": "foo_transaction"},
  861. timestamp=self.min_ago,
  862. )
  863. self.store_metric(
  864. 100,
  865. tags={"transaction": "bar_transaction"},
  866. timestamp=self.min_ago,
  867. )
  868. query = {
  869. "project": [self.project.id],
  870. "orderby": "p50()",
  871. "field": [
  872. "transaction",
  873. "count_unique(user)",
  874. "p50()",
  875. ],
  876. "dataset": "metricsEnhanced",
  877. "per_page": 50,
  878. }
  879. response = self.do_request(query)
  880. assert response.status_code == 200, response.content
  881. assert len(response.data["data"]) == 2
  882. data = response.data["data"]
  883. meta = response.data["meta"]
  884. assert data[0]["transaction"] == "foo_transaction"
  885. assert data[0]["count_unique(user)"] == 1
  886. assert data[1]["transaction"] == "bar_transaction"
  887. assert data[1]["count_unique(user)"] == 0
  888. assert meta["isMetricsData"]
  889. def test_sum_transaction_duration(self):
  890. self.store_metric(
  891. 50,
  892. tags={"transaction": "foo_transaction"},
  893. timestamp=self.min_ago,
  894. )
  895. self.store_metric(
  896. 100,
  897. tags={"transaction": "foo_transaction"},
  898. timestamp=self.min_ago,
  899. )
  900. self.store_metric(
  901. 150,
  902. tags={"transaction": "foo_transaction"},
  903. timestamp=self.min_ago,
  904. )
  905. query = {
  906. "project": [self.project.id],
  907. "orderby": "sum(transaction.duration)",
  908. "field": [
  909. "transaction",
  910. "sum(transaction.duration)",
  911. ],
  912. "dataset": "metricsEnhanced",
  913. "per_page": 50,
  914. }
  915. response = self.do_request(query)
  916. assert response.status_code == 200, response.content
  917. assert len(response.data["data"]) == 1
  918. data = response.data["data"]
  919. meta = response.data["meta"]
  920. assert data[0]["transaction"] == "foo_transaction"
  921. assert data[0]["sum(transaction.duration)"] == 300
  922. assert meta["isMetricsData"]
  923. def test_custom_measurements_simple(self):
  924. self.store_metric(
  925. 1,
  926. metric="measurements.something_custom",
  927. internal_metric="d:transactions/measurements.something_custom@millisecond",
  928. entity="metrics_distributions",
  929. tags={"transaction": "foo_transaction"},
  930. timestamp=self.min_ago,
  931. )
  932. query = {
  933. "project": [self.project.id],
  934. "orderby": "p50(measurements.something_custom)",
  935. "field": [
  936. "transaction",
  937. "p50(measurements.something_custom)",
  938. ],
  939. "statsPeriod": "24h",
  940. "dataset": "metricsEnhanced",
  941. "per_page": 50,
  942. }
  943. response = self.do_request(query)
  944. assert response.status_code == 200, response.content
  945. assert len(response.data["data"]) == 1
  946. data = response.data["data"]
  947. meta = response.data["meta"]
  948. assert data[0]["transaction"] == "foo_transaction"
  949. assert data[0]["p50(measurements.something_custom)"] == 1
  950. assert meta["isMetricsData"]