test_organization_events_mep.py 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  1. from unittest import mock
  2. import pytest
  3. from django.urls import reverse
  4. from snuba_sdk.conditions import InvalidConditionError
  5. from sentry.discover.models import TeamKeyTransaction
  6. from sentry.exceptions import IncompatibleMetricsQuery, InvalidSearchQuery
  7. from sentry.models import ProjectTeam
  8. from sentry.models.transaction_threshold import (
  9. ProjectTransactionThresholdOverride,
  10. TransactionMetric,
  11. )
  12. from sentry.search.events import constants
  13. from sentry.testutils import MetricsEnhancedPerformanceTestCase
  14. from sentry.testutils.helpers.datetime import before_now, iso_format
  15. from sentry.utils.samples import load_data
  16. pytestmark = pytest.mark.sentry_metrics
  17. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  18. viewname = "sentry-api-0-organization-events"
  19. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  20. METRIC_STRINGS = [
  21. "foo_transaction",
  22. "bar_transaction",
  23. "baz_transaction",
  24. "staging",
  25. "measurement_rating",
  26. "good",
  27. "meh",
  28. "d:transactions/measurements.something_custom@millisecond",
  29. "d:transactions/measurements.runtime@hour",
  30. "d:transactions/measurements.bytes_transfered@byte",
  31. "d:transactions/measurements.datacenter_memory@pebibyte",
  32. "d:transactions/measurements.longtaskcount@none",
  33. "d:transactions/measurements.percent@ratio",
  34. "d:transactions/measurements.custom_type@somethingcustom",
  35. ]
  36. def setUp(self):
  37. super().setUp()
  38. self.min_ago = before_now(minutes=1)
  39. self.two_min_ago = before_now(minutes=2)
  40. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  41. self.features = {
  42. "organizations:performance-use-metrics": True,
  43. }
  44. def do_request(self, query, features=None):
  45. if features is None:
  46. features = {"organizations:discover-basic": True}
  47. features.update(self.features)
  48. self.login_as(user=self.user)
  49. url = reverse(
  50. self.viewname,
  51. kwargs={"organization_slug": self.organization.slug},
  52. )
  53. with self.feature(features):
  54. return self.client.get(url, query, format="json")
  55. def test_no_projects(self):
  56. response = self.do_request(
  57. {
  58. "dataset": "metricsEnhanced",
  59. }
  60. )
  61. assert response.status_code == 200, response.content
  62. def test_invalid_dataset(self):
  63. response = self.do_request(
  64. {
  65. "dataset": "aFakeDataset",
  66. "project": self.project.id,
  67. }
  68. )
  69. assert response.status_code == 400, response.content
  70. assert (
  71. response.data["detail"] == "dataset must be one of: discover, metricsEnhanced, metrics"
  72. )
  73. def test_out_of_retention(self):
  74. self.create_project()
  75. with self.options({"system.event-retention-days": 10}):
  76. query = {
  77. "field": ["id", "timestamp"],
  78. "orderby": ["-timestamp", "-id"],
  79. "query": "event.type:transaction",
  80. "start": iso_format(before_now(days=20)),
  81. "end": iso_format(before_now(days=15)),
  82. "dataset": "metricsEnhanced",
  83. }
  84. response = self.do_request(query)
  85. assert response.status_code == 400, response.content
  86. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  87. def test_invalid_search_terms(self):
  88. response = self.do_request(
  89. {
  90. "field": ["epm()"],
  91. "query": "hi \n there",
  92. "project": self.project.id,
  93. "dataset": "metricsEnhanced",
  94. }
  95. )
  96. assert response.status_code == 400, response.content
  97. assert (
  98. response.data["detail"]
  99. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  100. )
  101. def test_percentile_with_no_data(self):
  102. response = self.do_request(
  103. {
  104. "field": ["p50()"],
  105. "query": "",
  106. "project": self.project.id,
  107. "dataset": "metricsEnhanced",
  108. }
  109. )
  110. assert response.status_code == 200, response.content
  111. data = response.data["data"]
  112. assert len(data) == 1
  113. assert data[0]["p50()"] == 0
  114. def test_project_name(self):
  115. self.store_transaction_metric(
  116. 1,
  117. tags={"environment": "staging"},
  118. timestamp=self.min_ago,
  119. )
  120. response = self.do_request(
  121. {
  122. "field": ["project.name", "environment", "epm()"],
  123. "query": "event.type:transaction",
  124. "dataset": "metricsEnhanced",
  125. "per_page": 50,
  126. }
  127. )
  128. assert response.status_code == 200, response.content
  129. assert len(response.data["data"]) == 1
  130. data = response.data["data"]
  131. meta = response.data["meta"]
  132. field_meta = meta["fields"]
  133. assert data[0]["project.name"] == self.project.slug
  134. assert "project.id" not in data[0]
  135. assert data[0]["environment"] == "staging"
  136. assert meta["isMetricsData"]
  137. assert field_meta["project.name"] == "string"
  138. assert field_meta["environment"] == "string"
  139. assert field_meta["epm()"] == "number"
  140. def test_title_alias(self):
  141. """title is an alias to transaction name"""
  142. self.store_transaction_metric(
  143. 1,
  144. tags={"transaction": "foo_transaction"},
  145. timestamp=self.min_ago,
  146. )
  147. response = self.do_request(
  148. {
  149. "field": ["title", "p50()"],
  150. "query": "event.type:transaction",
  151. "dataset": "metricsEnhanced",
  152. "per_page": 50,
  153. }
  154. )
  155. assert response.status_code == 200, response.content
  156. assert len(response.data["data"]) == 1
  157. data = response.data["data"]
  158. meta = response.data["meta"]
  159. field_meta = meta["fields"]
  160. assert data[0]["title"] == "foo_transaction"
  161. assert data[0]["p50()"] == 1
  162. assert meta["isMetricsData"]
  163. assert field_meta["title"] == "string"
  164. assert field_meta["p50()"] == "duration"
  165. def test_having_condition(self):
  166. self.store_transaction_metric(
  167. 1,
  168. tags={"environment": "staging", "transaction": "foo_transaction"},
  169. timestamp=self.min_ago,
  170. )
  171. self.store_transaction_metric(
  172. # shouldn't show up
  173. 100,
  174. tags={"environment": "staging", "transaction": "bar_transaction"},
  175. timestamp=self.min_ago,
  176. )
  177. response = self.do_request(
  178. {
  179. "field": ["transaction", "project", "p50(transaction.duration)"],
  180. "query": "event.type:transaction p50(transaction.duration):<50",
  181. "dataset": "metricsEnhanced",
  182. "per_page": 50,
  183. }
  184. )
  185. assert response.status_code == 200, response.content
  186. assert len(response.data["data"]) == 1
  187. data = response.data["data"]
  188. meta = response.data["meta"]
  189. field_meta = meta["fields"]
  190. assert data[0]["transaction"] == "foo_transaction"
  191. assert data[0]["project"] == self.project.slug
  192. assert data[0]["p50(transaction.duration)"] == 1
  193. assert meta["isMetricsData"]
  194. assert field_meta["transaction"] == "string"
  195. assert field_meta["project"] == "string"
  196. assert field_meta["p50(transaction.duration)"] == "duration"
  197. def test_having_condition_with_preventing_aggregates(self):
  198. self.store_transaction_metric(
  199. 1,
  200. tags={"environment": "staging", "transaction": "foo_transaction"},
  201. timestamp=self.min_ago,
  202. )
  203. self.store_transaction_metric(
  204. 100,
  205. tags={"environment": "staging", "transaction": "bar_transaction"},
  206. timestamp=self.min_ago,
  207. )
  208. response = self.do_request(
  209. {
  210. "field": ["transaction", "project", "p50(transaction.duration)"],
  211. "query": "event.type:transaction p50(transaction.duration):<50",
  212. "dataset": "metricsEnhanced",
  213. "preventMetricAggregates": "1",
  214. "per_page": 50,
  215. }
  216. )
  217. assert response.status_code == 200, response.content
  218. assert len(response.data["data"]) == 0
  219. meta = response.data["meta"]
  220. field_meta = meta["fields"]
  221. assert not meta["isMetricsData"]
  222. assert field_meta["transaction"] == "string"
  223. assert field_meta["project"] == "string"
  224. assert field_meta["p50(transaction.duration)"] == "duration"
  225. def test_having_condition_with_preventing_aggregate_metrics_only(self):
  226. """same as the previous test, but with the dataset on explicit metrics
  227. which should throw a 400 error instead"""
  228. response = self.do_request(
  229. {
  230. "field": ["transaction", "project", "p50(transaction.duration)"],
  231. "query": "event.type:transaction p50(transaction.duration):<50",
  232. "dataset": "metrics",
  233. "preventMetricAggregates": "1",
  234. "per_page": 50,
  235. "project": self.project.id,
  236. }
  237. )
  238. assert response.status_code == 400, response.content
  239. def test_having_condition_not_selected(self):
  240. self.store_transaction_metric(
  241. 1,
  242. tags={"environment": "staging", "transaction": "foo_transaction"},
  243. timestamp=self.min_ago,
  244. )
  245. self.store_transaction_metric(
  246. # shouldn't show up
  247. 100,
  248. tags={"environment": "staging", "transaction": "bar_transaction"},
  249. timestamp=self.min_ago,
  250. )
  251. response = self.do_request(
  252. {
  253. "field": ["transaction", "project", "p50(transaction.duration)"],
  254. "query": "event.type:transaction p75(transaction.duration):<50",
  255. "dataset": "metricsEnhanced",
  256. "per_page": 50,
  257. }
  258. )
  259. assert response.status_code == 200, response.content
  260. assert len(response.data["data"]) == 1
  261. data = response.data["data"]
  262. meta = response.data["meta"]
  263. field_meta = meta["fields"]
  264. assert data[0]["transaction"] == "foo_transaction"
  265. assert data[0]["project"] == self.project.slug
  266. assert data[0]["p50(transaction.duration)"] == 1
  267. assert meta["isMetricsData"]
  268. assert field_meta["transaction"] == "string"
  269. assert field_meta["project"] == "string"
  270. assert field_meta["p50(transaction.duration)"] == "duration"
  271. def test_non_metrics_tag_with_implicit_format(self):
  272. self.store_transaction_metric(
  273. 1,
  274. tags={"environment": "staging", "transaction": "foo_transaction"},
  275. timestamp=self.min_ago,
  276. )
  277. response = self.do_request(
  278. {
  279. "field": ["test", "p50(transaction.duration)"],
  280. "query": "event.type:transaction",
  281. "dataset": "metricsEnhanced",
  282. "per_page": 50,
  283. }
  284. )
  285. assert response.status_code == 200, response.content
  286. assert len(response.data["data"]) == 0
  287. assert not response.data["meta"]["isMetricsData"]
  288. def test_non_metrics_tag_with_implicit_format_metrics_dataset(self):
  289. self.store_transaction_metric(
  290. 1,
  291. tags={"environment": "staging", "transaction": "foo_transaction"},
  292. timestamp=self.min_ago,
  293. )
  294. response = self.do_request(
  295. {
  296. "field": ["test", "p50(transaction.duration)"],
  297. "query": "event.type:transaction",
  298. "dataset": "metrics",
  299. "per_page": 50,
  300. }
  301. )
  302. assert response.status_code == 400, response.content
  303. def test_performance_homepage_query(self):
  304. self.store_transaction_metric(
  305. 1,
  306. tags={
  307. "transaction": "foo_transaction",
  308. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  309. },
  310. timestamp=self.min_ago,
  311. )
  312. self.store_transaction_metric(
  313. 1,
  314. "measurements.fcp",
  315. tags={"transaction": "foo_transaction"},
  316. timestamp=self.min_ago,
  317. )
  318. self.store_transaction_metric(
  319. 2,
  320. "measurements.lcp",
  321. tags={"transaction": "foo_transaction"},
  322. timestamp=self.min_ago,
  323. )
  324. self.store_transaction_metric(
  325. 3,
  326. "measurements.fid",
  327. tags={"transaction": "foo_transaction"},
  328. timestamp=self.min_ago,
  329. )
  330. self.store_transaction_metric(
  331. 4,
  332. "measurements.cls",
  333. tags={"transaction": "foo_transaction"},
  334. timestamp=self.min_ago,
  335. )
  336. self.store_transaction_metric(
  337. 1,
  338. "user",
  339. tags={
  340. "transaction": "foo_transaction",
  341. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
  342. },
  343. timestamp=self.min_ago,
  344. )
  345. for dataset in ["metrics", "metricsEnhanced"]:
  346. response = self.do_request(
  347. {
  348. "field": [
  349. "transaction",
  350. "project",
  351. "tpm()",
  352. "p75(measurements.fcp)",
  353. "p75(measurements.lcp)",
  354. "p75(measurements.fid)",
  355. "p75(measurements.cls)",
  356. "count_unique(user)",
  357. "apdex()",
  358. "count_miserable(user)",
  359. "user_misery()",
  360. "failure_rate()",
  361. ],
  362. "query": "event.type:transaction",
  363. "dataset": dataset,
  364. "per_page": 50,
  365. }
  366. )
  367. assert len(response.data["data"]) == 1
  368. data = response.data["data"][0]
  369. meta = response.data["meta"]
  370. field_meta = meta["fields"]
  371. assert data["transaction"] == "foo_transaction"
  372. assert data["project"] == self.project.slug
  373. assert data["p75(measurements.fcp)"] == 1.0
  374. assert data["p75(measurements.lcp)"] == 2.0
  375. assert data["p75(measurements.fid)"] == 3.0
  376. assert data["p75(measurements.cls)"] == 4.0
  377. assert data["apdex()"] == 1.0
  378. assert data["count_miserable(user)"] == 1.0
  379. assert data["user_misery()"] == 0.058
  380. assert data["failure_rate()"] == 1
  381. assert meta["isMetricsData"]
  382. assert field_meta["transaction"] == "string"
  383. assert field_meta["project"] == "string"
  384. assert field_meta["p75(measurements.fcp)"] == "duration"
  385. assert field_meta["p75(measurements.lcp)"] == "duration"
  386. assert field_meta["p75(measurements.fid)"] == "duration"
  387. assert field_meta["p75(measurements.cls)"] == "number"
  388. assert field_meta["apdex()"] == "number"
  389. assert field_meta["count_miserable(user)"] == "integer"
  390. assert field_meta["user_misery()"] == "number"
  391. assert field_meta["failure_rate()"] == "percentage"
  392. def test_no_team_key_transactions(self):
  393. self.store_transaction_metric(
  394. 1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago
  395. )
  396. self.store_transaction_metric(
  397. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  398. )
  399. query = {
  400. "team": "myteams",
  401. "project": [self.project.id],
  402. # TODO sort by transaction here once that's possible for order to match the same test without metrics
  403. "orderby": "p95()",
  404. "field": [
  405. "team_key_transaction",
  406. "transaction",
  407. "transaction.status",
  408. "project",
  409. "epm()",
  410. "failure_rate()",
  411. "p95()",
  412. ],
  413. "per_page": 50,
  414. "dataset": "metricsEnhanced",
  415. }
  416. response = self.do_request(query)
  417. assert response.status_code == 200, response.content
  418. assert len(response.data["data"]) == 2
  419. data = response.data["data"]
  420. meta = response.data["meta"]
  421. field_meta = meta["fields"]
  422. assert data[0]["team_key_transaction"] == 0
  423. assert data[0]["transaction"] == "foo_transaction"
  424. assert data[1]["team_key_transaction"] == 0
  425. assert data[1]["transaction"] == "bar_transaction"
  426. assert meta["isMetricsData"]
  427. assert field_meta["team_key_transaction"] == "boolean"
  428. assert field_meta["transaction"] == "string"
  429. def test_team_key_transactions_my_teams(self):
  430. team1 = self.create_team(organization=self.organization, name="Team A")
  431. self.create_team_membership(team1, user=self.user)
  432. self.project.add_team(team1)
  433. team2 = self.create_team(organization=self.organization, name="Team B")
  434. self.project.add_team(team2)
  435. key_transactions = [
  436. (team1, "foo_transaction"),
  437. (team2, "baz_transaction"),
  438. ]
  439. # Not a key transaction
  440. self.store_transaction_metric(
  441. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  442. )
  443. for team, transaction in key_transactions:
  444. self.store_transaction_metric(
  445. 1, tags={"transaction": transaction}, timestamp=self.min_ago
  446. )
  447. TeamKeyTransaction.objects.create(
  448. organization=self.organization,
  449. transaction=transaction,
  450. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  451. )
  452. query = {
  453. "team": "myteams",
  454. "project": [self.project.id],
  455. "field": [
  456. "team_key_transaction",
  457. "transaction",
  458. "transaction.status",
  459. "project",
  460. "epm()",
  461. "failure_rate()",
  462. "p95()",
  463. ],
  464. "per_page": 50,
  465. "dataset": "metricsEnhanced",
  466. }
  467. query["orderby"] = ["team_key_transaction", "p95()"]
  468. response = self.do_request(query)
  469. assert response.status_code == 200, response.content
  470. assert len(response.data["data"]) == 3
  471. data = response.data["data"]
  472. meta = response.data["meta"]
  473. field_meta = meta["fields"]
  474. assert data[0]["team_key_transaction"] == 0
  475. assert data[0]["transaction"] == "baz_transaction"
  476. assert data[1]["team_key_transaction"] == 0
  477. assert data[1]["transaction"] == "bar_transaction"
  478. assert data[2]["team_key_transaction"] == 1
  479. assert data[2]["transaction"] == "foo_transaction"
  480. assert meta["isMetricsData"]
  481. assert field_meta["team_key_transaction"] == "boolean"
  482. assert field_meta["transaction"] == "string"
  483. # not specifying any teams should use my teams
  484. query = {
  485. "project": [self.project.id],
  486. "field": [
  487. "team_key_transaction",
  488. "transaction",
  489. "transaction.status",
  490. "project",
  491. "epm()",
  492. "failure_rate()",
  493. "p95()",
  494. ],
  495. "per_page": 50,
  496. "dataset": "metricsEnhanced",
  497. }
  498. query["orderby"] = ["team_key_transaction", "p95()"]
  499. response = self.do_request(query)
  500. assert response.status_code == 200, response.content
  501. assert len(response.data["data"]) == 3
  502. data = response.data["data"]
  503. meta = response.data["meta"]
  504. field_meta = meta["fields"]
  505. assert data[0]["team_key_transaction"] == 0
  506. assert data[0]["transaction"] == "baz_transaction"
  507. assert data[1]["team_key_transaction"] == 0
  508. assert data[1]["transaction"] == "bar_transaction"
  509. assert data[2]["team_key_transaction"] == 1
  510. assert data[2]["transaction"] == "foo_transaction"
  511. assert meta["isMetricsData"]
  512. assert field_meta["team_key_transaction"] == "boolean"
  513. assert field_meta["transaction"] == "string"
  514. def test_team_key_transactions_orderby(self):
  515. team1 = self.create_team(organization=self.organization, name="Team A")
  516. team2 = self.create_team(organization=self.organization, name="Team B")
  517. key_transactions = [
  518. (team1, "foo_transaction", 1),
  519. (team2, "baz_transaction", 100),
  520. ]
  521. # Not a key transaction
  522. self.store_transaction_metric(
  523. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  524. )
  525. for team, transaction, value in key_transactions:
  526. self.store_transaction_metric(
  527. value, tags={"transaction": transaction}, timestamp=self.min_ago
  528. )
  529. self.create_team_membership(team, user=self.user)
  530. self.project.add_team(team)
  531. TeamKeyTransaction.objects.create(
  532. organization=self.organization,
  533. transaction=transaction,
  534. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  535. )
  536. query = {
  537. "team": "myteams",
  538. "project": [self.project.id],
  539. "field": [
  540. "team_key_transaction",
  541. "transaction",
  542. "transaction.status",
  543. "project",
  544. "epm()",
  545. "failure_rate()",
  546. "p95()",
  547. ],
  548. "per_page": 50,
  549. "dataset": "metricsEnhanced",
  550. }
  551. # test ascending order
  552. query["orderby"] = ["team_key_transaction", "p95()"]
  553. response = self.do_request(query)
  554. assert response.status_code == 200, response.content
  555. assert len(response.data["data"]) == 3
  556. data = response.data["data"]
  557. meta = response.data["meta"]
  558. field_meta = meta["fields"]
  559. assert data[0]["team_key_transaction"] == 0
  560. assert data[0]["transaction"] == "bar_transaction"
  561. assert data[1]["team_key_transaction"] == 1
  562. assert data[1]["transaction"] == "foo_transaction"
  563. assert data[2]["team_key_transaction"] == 1
  564. assert data[2]["transaction"] == "baz_transaction"
  565. assert meta["isMetricsData"]
  566. assert field_meta["team_key_transaction"] == "boolean"
  567. assert field_meta["transaction"] == "string"
  568. # test descending order
  569. query["orderby"] = ["-team_key_transaction", "p95()"]
  570. response = self.do_request(query)
  571. assert response.status_code == 200, response.content
  572. assert len(response.data["data"]) == 3
  573. data = response.data["data"]
  574. meta = response.data["meta"]
  575. field_meta = meta["fields"]
  576. assert data[0]["team_key_transaction"] == 1
  577. assert data[0]["transaction"] == "foo_transaction"
  578. assert data[1]["team_key_transaction"] == 1
  579. assert data[1]["transaction"] == "baz_transaction"
  580. assert data[2]["team_key_transaction"] == 0
  581. assert data[2]["transaction"] == "bar_transaction"
  582. assert meta["isMetricsData"]
  583. assert field_meta["team_key_transaction"] == "boolean"
  584. assert field_meta["transaction"] == "string"
  585. def test_team_key_transactions_query(self):
  586. team1 = self.create_team(organization=self.organization, name="Team A")
  587. team2 = self.create_team(organization=self.organization, name="Team B")
  588. key_transactions = [
  589. (team1, "foo_transaction", 1),
  590. (team2, "baz_transaction", 100),
  591. ]
  592. # Not a key transaction
  593. self.store_transaction_metric(
  594. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  595. )
  596. for team, transaction, value in key_transactions:
  597. self.store_transaction_metric(
  598. value, tags={"transaction": transaction}, timestamp=self.min_ago
  599. )
  600. self.create_team_membership(team, user=self.user)
  601. self.project.add_team(team)
  602. TeamKeyTransaction.objects.create(
  603. organization=self.organization,
  604. transaction=transaction,
  605. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  606. )
  607. query = {
  608. "team": "myteams",
  609. "project": [self.project.id],
  610. # use the order by to ensure the result order
  611. "orderby": "p95()",
  612. "field": [
  613. "team_key_transaction",
  614. "transaction",
  615. "transaction.status",
  616. "project",
  617. "epm()",
  618. "failure_rate()",
  619. "p95()",
  620. ],
  621. "per_page": 50,
  622. "dataset": "metricsEnhanced",
  623. }
  624. # key transactions
  625. query["query"] = "has:team_key_transaction"
  626. response = self.do_request(query)
  627. assert response.status_code == 200, response.content
  628. assert len(response.data["data"]) == 2
  629. data = response.data["data"]
  630. meta = response.data["meta"]
  631. field_meta = meta["fields"]
  632. assert data[0]["team_key_transaction"] == 1
  633. assert data[0]["transaction"] == "foo_transaction"
  634. assert data[1]["team_key_transaction"] == 1
  635. assert data[1]["transaction"] == "baz_transaction"
  636. assert meta["isMetricsData"]
  637. assert field_meta["team_key_transaction"] == "boolean"
  638. assert field_meta["transaction"] == "string"
  639. # key transactions
  640. query["query"] = "team_key_transaction:true"
  641. response = self.do_request(query)
  642. assert response.status_code == 200, response.content
  643. assert len(response.data["data"]) == 2
  644. data = response.data["data"]
  645. meta = response.data["meta"]
  646. field_meta = meta["fields"]
  647. assert data[0]["team_key_transaction"] == 1
  648. assert data[0]["transaction"] == "foo_transaction"
  649. assert data[1]["team_key_transaction"] == 1
  650. assert data[1]["transaction"] == "baz_transaction"
  651. assert meta["isMetricsData"]
  652. assert field_meta["team_key_transaction"] == "boolean"
  653. assert field_meta["transaction"] == "string"
  654. # not key transactions
  655. query["query"] = "!has:team_key_transaction"
  656. response = self.do_request(query)
  657. assert response.status_code == 200, response.content
  658. assert len(response.data["data"]) == 1
  659. data = response.data["data"]
  660. meta = response.data["meta"]
  661. field_meta = meta["fields"]
  662. assert data[0]["team_key_transaction"] == 0
  663. assert data[0]["transaction"] == "bar_transaction"
  664. assert meta["isMetricsData"]
  665. assert field_meta["team_key_transaction"] == "boolean"
  666. assert field_meta["transaction"] == "string"
  667. # not key transactions
  668. query["query"] = "team_key_transaction:false"
  669. response = self.do_request(query)
  670. assert response.status_code == 200, response.content
  671. assert len(response.data["data"]) == 1
  672. data = response.data["data"]
  673. meta = response.data["meta"]
  674. field_meta = meta["fields"]
  675. assert data[0]["team_key_transaction"] == 0
  676. assert data[0]["transaction"] == "bar_transaction"
  677. assert meta["isMetricsData"]
  678. assert field_meta["team_key_transaction"] == "boolean"
  679. assert field_meta["transaction"] == "string"
  680. def test_too_many_team_key_transactions(self):
  681. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  682. with mock.patch(
  683. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  684. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  685. ):
  686. team = self.create_team(organization=self.organization, name="Team A")
  687. self.create_team_membership(team, user=self.user)
  688. self.project.add_team(team)
  689. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  690. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  691. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  692. self.store_transaction_metric(
  693. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  694. )
  695. TeamKeyTransaction.objects.bulk_create(
  696. [
  697. TeamKeyTransaction(
  698. organization=self.organization,
  699. project_team=project_team,
  700. transaction=transactions[i],
  701. )
  702. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  703. ]
  704. )
  705. query = {
  706. "team": "myteams",
  707. "project": [self.project.id],
  708. "orderby": "p95()",
  709. "field": [
  710. "team_key_transaction",
  711. "transaction",
  712. "transaction.status",
  713. "project",
  714. "epm()",
  715. "failure_rate()",
  716. "p95()",
  717. ],
  718. "dataset": "metricsEnhanced",
  719. "per_page": 50,
  720. }
  721. response = self.do_request(query)
  722. assert response.status_code == 200, response.content
  723. assert len(response.data["data"]) == 2
  724. data = response.data["data"]
  725. meta = response.data["meta"]
  726. assert (
  727. sum(row["team_key_transaction"] for row in data)
  728. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  729. )
  730. assert meta["isMetricsData"]
  731. def test_measurement_rating(self):
  732. self.store_transaction_metric(
  733. 50,
  734. metric="measurements.lcp",
  735. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  736. timestamp=self.min_ago,
  737. )
  738. self.store_transaction_metric(
  739. 15,
  740. metric="measurements.fp",
  741. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  742. timestamp=self.min_ago,
  743. )
  744. self.store_transaction_metric(
  745. 1500,
  746. metric="measurements.fcp",
  747. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  748. timestamp=self.min_ago,
  749. )
  750. self.store_transaction_metric(
  751. 125,
  752. metric="measurements.fid",
  753. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  754. timestamp=self.min_ago,
  755. )
  756. self.store_transaction_metric(
  757. 0.15,
  758. metric="measurements.cls",
  759. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  760. timestamp=self.min_ago,
  761. )
  762. response = self.do_request(
  763. {
  764. "field": [
  765. "transaction",
  766. "count_web_vitals(measurements.lcp, good)",
  767. "count_web_vitals(measurements.fp, good)",
  768. "count_web_vitals(measurements.fcp, meh)",
  769. "count_web_vitals(measurements.fid, meh)",
  770. "count_web_vitals(measurements.cls, good)",
  771. ],
  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. field_meta = meta["fields"]
  782. assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
  783. assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
  784. assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
  785. assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
  786. assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
  787. assert meta["isMetricsData"]
  788. assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
  789. assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
  790. assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
  791. assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
  792. assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
  793. def test_measurement_rating_that_does_not_exist(self):
  794. self.store_transaction_metric(
  795. 1,
  796. metric="measurements.lcp",
  797. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  798. timestamp=self.min_ago,
  799. )
  800. response = self.do_request(
  801. {
  802. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  803. "query": "event.type:transaction",
  804. "dataset": "metricsEnhanced",
  805. "per_page": 50,
  806. }
  807. )
  808. assert response.status_code == 200, response.content
  809. assert len(response.data["data"]) == 1
  810. data = response.data["data"]
  811. meta = response.data["meta"]
  812. assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
  813. assert meta["isMetricsData"]
  814. assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
  815. def test_count_web_vitals_invalid_vital(self):
  816. query = {
  817. "field": [
  818. "count_web_vitals(measurements.foo, poor)",
  819. ],
  820. "project": [self.project.id],
  821. "dataset": "metricsEnhanced",
  822. }
  823. response = self.do_request(query)
  824. assert response.status_code == 400, response.content
  825. query = {
  826. "field": [
  827. "count_web_vitals(tags[lcp], poor)",
  828. ],
  829. "project": [self.project.id],
  830. "dataset": "metricsEnhanced",
  831. }
  832. response = self.do_request(query)
  833. assert response.status_code == 400, response.content
  834. query = {
  835. "field": [
  836. "count_web_vitals(transaction.duration, poor)",
  837. ],
  838. "project": [self.project.id],
  839. "dataset": "metricsEnhanced",
  840. }
  841. response = self.do_request(query)
  842. assert response.status_code == 400, response.content
  843. query = {
  844. "field": [
  845. "count_web_vitals(measurements.lcp, bad)",
  846. ],
  847. "project": [self.project.id],
  848. "dataset": "metricsEnhanced",
  849. }
  850. response = self.do_request(query)
  851. assert response.status_code == 400, response.content
  852. @mock.patch("sentry.snuba.metrics_performance.MetricsQueryBuilder")
  853. def test_failed_dry_run_does_not_error(self, mock_builder):
  854. with self.feature("organizations:performance-dry-run-mep"):
  855. mock_builder.side_effect = InvalidSearchQuery("Something bad")
  856. query = {
  857. "field": ["count()"],
  858. "project": [self.project.id],
  859. }
  860. response = self.do_request(query)
  861. assert response.status_code == 200, response.content
  862. assert len(mock_builder.mock_calls) == 1
  863. assert mock_builder.call_args.kwargs["dry_run"]
  864. mock_builder.side_effect = IncompatibleMetricsQuery("Something bad")
  865. query = {
  866. "field": ["count()"],
  867. "project": [self.project.id],
  868. }
  869. response = self.do_request(query)
  870. assert response.status_code == 200, response.content
  871. assert len(mock_builder.mock_calls) == 2
  872. assert mock_builder.call_args.kwargs["dry_run"]
  873. mock_builder.side_effect = InvalidConditionError("Something bad")
  874. query = {
  875. "field": ["count()"],
  876. "project": [self.project.id],
  877. }
  878. response = self.do_request(query)
  879. assert response.status_code == 200, response.content
  880. assert len(mock_builder.mock_calls) == 3
  881. assert mock_builder.call_args.kwargs["dry_run"]
  882. def test_count_unique_user_returns_zero(self):
  883. self.store_transaction_metric(
  884. 50,
  885. metric="user",
  886. tags={"transaction": "foo_transaction"},
  887. timestamp=self.min_ago,
  888. )
  889. self.store_transaction_metric(
  890. 50,
  891. tags={"transaction": "foo_transaction"},
  892. timestamp=self.min_ago,
  893. )
  894. self.store_transaction_metric(
  895. 100,
  896. tags={"transaction": "bar_transaction"},
  897. timestamp=self.min_ago,
  898. )
  899. query = {
  900. "project": [self.project.id],
  901. "orderby": "p50()",
  902. "field": [
  903. "transaction",
  904. "count_unique(user)",
  905. "p50()",
  906. ],
  907. "dataset": "metricsEnhanced",
  908. "per_page": 50,
  909. }
  910. response = self.do_request(query)
  911. assert response.status_code == 200, response.content
  912. assert len(response.data["data"]) == 2
  913. data = response.data["data"]
  914. meta = response.data["meta"]
  915. assert data[0]["transaction"] == "foo_transaction"
  916. assert data[0]["count_unique(user)"] == 1
  917. assert data[1]["transaction"] == "bar_transaction"
  918. assert data[1]["count_unique(user)"] == 0
  919. assert meta["isMetricsData"]
  920. def test_sum_transaction_duration(self):
  921. self.store_transaction_metric(
  922. 50,
  923. tags={"transaction": "foo_transaction"},
  924. timestamp=self.min_ago,
  925. )
  926. self.store_transaction_metric(
  927. 100,
  928. tags={"transaction": "foo_transaction"},
  929. timestamp=self.min_ago,
  930. )
  931. self.store_transaction_metric(
  932. 150,
  933. tags={"transaction": "foo_transaction"},
  934. timestamp=self.min_ago,
  935. )
  936. query = {
  937. "project": [self.project.id],
  938. "orderby": "sum(transaction.duration)",
  939. "field": [
  940. "transaction",
  941. "sum(transaction.duration)",
  942. ],
  943. "dataset": "metricsEnhanced",
  944. "per_page": 50,
  945. }
  946. response = self.do_request(query)
  947. assert response.status_code == 200, response.content
  948. assert len(response.data["data"]) == 1
  949. data = response.data["data"]
  950. meta = response.data["meta"]
  951. assert data[0]["transaction"] == "foo_transaction"
  952. assert data[0]["sum(transaction.duration)"] == 300
  953. assert meta["isMetricsData"]
  954. def test_custom_measurements_simple(self):
  955. self.store_transaction_metric(
  956. 1,
  957. metric="measurements.something_custom",
  958. internal_metric="d:transactions/measurements.something_custom@millisecond",
  959. entity="metrics_distributions",
  960. tags={"transaction": "foo_transaction"},
  961. timestamp=self.min_ago,
  962. )
  963. query = {
  964. "project": [self.project.id],
  965. "orderby": "p50(measurements.something_custom)",
  966. "field": [
  967. "transaction",
  968. "p50(measurements.something_custom)",
  969. ],
  970. "statsPeriod": "24h",
  971. "dataset": "metricsEnhanced",
  972. "per_page": 50,
  973. }
  974. response = self.do_request(query)
  975. assert response.status_code == 200, response.content
  976. assert len(response.data["data"]) == 1
  977. data = response.data["data"]
  978. meta = response.data["meta"]
  979. assert data[0]["transaction"] == "foo_transaction"
  980. assert data[0]["p50(measurements.something_custom)"] == 1
  981. assert meta["isMetricsData"]
  982. assert meta["fields"]["p50(measurements.something_custom)"] == "duration"
  983. assert meta["units"]["p50(measurements.something_custom)"] == "millisecond"
  984. def test_custom_measurement_size_meta_type(self):
  985. self.store_transaction_metric(
  986. 100,
  987. metric="measurements.custom_type",
  988. internal_metric="d:transactions/measurements.custom_type@somethingcustom",
  989. entity="metrics_distributions",
  990. tags={"transaction": "foo_transaction"},
  991. timestamp=self.min_ago,
  992. )
  993. self.store_transaction_metric(
  994. 100,
  995. metric="measurements.percent",
  996. internal_metric="d:transactions/measurements.percent@ratio",
  997. entity="metrics_distributions",
  998. tags={"transaction": "foo_transaction"},
  999. timestamp=self.min_ago,
  1000. )
  1001. self.store_transaction_metric(
  1002. 100,
  1003. metric="measurements.longtaskcount",
  1004. internal_metric="d:transactions/measurements.longtaskcount@none",
  1005. entity="metrics_distributions",
  1006. tags={"transaction": "foo_transaction"},
  1007. timestamp=self.min_ago,
  1008. )
  1009. query = {
  1010. "project": [self.project.id],
  1011. "orderby": "p50(measurements.longtaskcount)",
  1012. "field": [
  1013. "transaction",
  1014. "p50(measurements.longtaskcount)",
  1015. "p50(measurements.percent)",
  1016. "p50(measurements.custom_type)",
  1017. ],
  1018. "statsPeriod": "24h",
  1019. "dataset": "metricsEnhanced",
  1020. "per_page": 50,
  1021. }
  1022. response = self.do_request(query)
  1023. assert response.status_code == 200, response.content
  1024. assert len(response.data["data"]) == 1
  1025. data = response.data["data"]
  1026. meta = response.data["meta"]
  1027. assert data[0]["transaction"] == "foo_transaction"
  1028. assert data[0]["p50(measurements.longtaskcount)"] == 100
  1029. assert data[0]["p50(measurements.percent)"] == 100
  1030. assert data[0]["p50(measurements.custom_type)"] == 100
  1031. assert meta["isMetricsData"]
  1032. assert meta["fields"]["p50(measurements.longtaskcount)"] == "integer"
  1033. assert meta["units"]["p50(measurements.longtaskcount)"] is None
  1034. assert meta["fields"]["p50(measurements.percent)"] == "percentage"
  1035. assert meta["units"]["p50(measurements.percent)"] is None
  1036. assert meta["fields"]["p50(measurements.custom_type)"] == "number"
  1037. assert meta["units"]["p50(measurements.custom_type)"] is None
  1038. def test_custom_measurement_none_type(self):
  1039. self.store_transaction_metric(
  1040. 1,
  1041. metric="measurements.cls",
  1042. entity="metrics_distributions",
  1043. tags={"transaction": "foo_transaction"},
  1044. timestamp=self.min_ago,
  1045. )
  1046. query = {
  1047. "project": [self.project.id],
  1048. "orderby": "p75(measurements.cls)",
  1049. "field": [
  1050. "transaction",
  1051. "p75(measurements.cls)",
  1052. "p99(measurements.cls)",
  1053. "max(measurements.cls)",
  1054. ],
  1055. "statsPeriod": "24h",
  1056. "dataset": "metricsEnhanced",
  1057. "per_page": 50,
  1058. }
  1059. response = self.do_request(query)
  1060. assert response.status_code == 200, response.content
  1061. assert len(response.data["data"]) == 1
  1062. data = response.data["data"]
  1063. meta = response.data["meta"]
  1064. assert data[0]["transaction"] == "foo_transaction"
  1065. assert data[0]["p75(measurements.cls)"] == 1
  1066. assert data[0]["p99(measurements.cls)"] == 1
  1067. assert data[0]["max(measurements.cls)"] == 1
  1068. assert meta["isMetricsData"]
  1069. assert meta["fields"]["p75(measurements.cls)"] == "number"
  1070. assert meta["units"]["p75(measurements.cls)"] is None
  1071. assert meta["fields"]["p99(measurements.cls)"] == "number"
  1072. assert meta["units"]["p99(measurements.cls)"] is None
  1073. assert meta["fields"]["max(measurements.cls)"] == "number"
  1074. assert meta["units"]["max(measurements.cls)"] is None
  1075. def test_custom_measurement_size_filtering(self):
  1076. self.store_transaction_metric(
  1077. 1,
  1078. metric="measurements.runtime",
  1079. internal_metric="d:transactions/measurements.runtime@hour",
  1080. entity="metrics_distributions",
  1081. tags={"transaction": "foo_transaction"},
  1082. timestamp=self.min_ago,
  1083. )
  1084. self.store_transaction_metric(
  1085. 180,
  1086. metric="measurements.runtime",
  1087. internal_metric="d:transactions/measurements.runtime@hour",
  1088. entity="metrics_distributions",
  1089. tags={"transaction": "bar_transaction"},
  1090. timestamp=self.min_ago,
  1091. )
  1092. query = {
  1093. "project": [self.project.id],
  1094. "field": [
  1095. "transaction",
  1096. "max(measurements.runtime)",
  1097. ],
  1098. "query": "p50(measurements.runtime):>1wk",
  1099. "statsPeriod": "24h",
  1100. "dataset": "metricsEnhanced",
  1101. "per_page": 50,
  1102. }
  1103. response = self.do_request(query)
  1104. assert response.status_code == 200, response.content
  1105. assert len(response.data["data"]) == 1
  1106. data = response.data["data"]
  1107. meta = response.data["meta"]
  1108. assert data[0]["transaction"] == "bar_transaction"
  1109. assert data[0]["max(measurements.runtime)"] == 180
  1110. assert meta["isMetricsData"]
  1111. def test_custom_measurement_duration_filtering(self):
  1112. self.store_transaction_metric(
  1113. 1,
  1114. metric="measurements.bytes_transfered",
  1115. internal_metric="d:transactions/measurements.datacenter_memory@pebibyte",
  1116. entity="metrics_distributions",
  1117. tags={"transaction": "foo_transaction"},
  1118. timestamp=self.min_ago,
  1119. )
  1120. self.store_transaction_metric(
  1121. 100,
  1122. metric="measurements.bytes_transfered",
  1123. internal_metric="d:transactions/measurements.datacenter_memory@pebibyte",
  1124. entity="metrics_distributions",
  1125. tags={"transaction": "bar_transaction"},
  1126. timestamp=self.min_ago,
  1127. )
  1128. query = {
  1129. "project": [self.project.id],
  1130. "field": [
  1131. "transaction",
  1132. "max(measurements.datacenter_memory)",
  1133. ],
  1134. "query": "p50(measurements.datacenter_memory):>5pb",
  1135. "statsPeriod": "24h",
  1136. "dataset": "metricsEnhanced",
  1137. "per_page": 50,
  1138. }
  1139. response = self.do_request(query)
  1140. assert response.status_code == 200, response.content
  1141. assert len(response.data["data"]) == 1
  1142. data = response.data["data"]
  1143. meta = response.data["meta"]
  1144. assert data[0]["transaction"] == "bar_transaction"
  1145. assert data[0]["max(measurements.datacenter_memory)"] == 100
  1146. assert meta["isMetricsData"]
  1147. def test_environment_param(self):
  1148. self.create_environment(self.project, name="staging")
  1149. self.store_transaction_metric(
  1150. 1,
  1151. tags={"transaction": "foo_transaction", "environment": "staging"},
  1152. timestamp=self.min_ago,
  1153. )
  1154. self.store_transaction_metric(
  1155. 100,
  1156. tags={"transaction": "foo_transaction"},
  1157. timestamp=self.min_ago,
  1158. )
  1159. query = {
  1160. "project": [self.project.id],
  1161. "environment": "staging",
  1162. "orderby": "p50(transaction.duration)",
  1163. "field": [
  1164. "transaction",
  1165. "environment",
  1166. "p50(transaction.duration)",
  1167. ],
  1168. "statsPeriod": "24h",
  1169. "dataset": "metricsEnhanced",
  1170. "per_page": 50,
  1171. }
  1172. response = self.do_request(query)
  1173. assert response.status_code == 200, response.content
  1174. assert len(response.data["data"]) == 1
  1175. data = response.data["data"]
  1176. meta = response.data["meta"]
  1177. assert data[0]["transaction"] == "foo_transaction"
  1178. assert data[0]["environment"] == "staging"
  1179. assert data[0]["p50(transaction.duration)"] == 1
  1180. assert meta["isMetricsData"]
  1181. def test_environment_query(self):
  1182. self.create_environment(self.project, name="staging")
  1183. self.store_transaction_metric(
  1184. 1,
  1185. tags={"transaction": "foo_transaction", "environment": "staging"},
  1186. timestamp=self.min_ago,
  1187. )
  1188. self.store_transaction_metric(
  1189. 100,
  1190. tags={"transaction": "foo_transaction"},
  1191. timestamp=self.min_ago,
  1192. )
  1193. query = {
  1194. "project": [self.project.id],
  1195. "orderby": "p50(transaction.duration)",
  1196. "field": [
  1197. "transaction",
  1198. "environment",
  1199. "p50(transaction.duration)",
  1200. ],
  1201. "query": "!has:environment",
  1202. "statsPeriod": "24h",
  1203. "dataset": "metricsEnhanced",
  1204. "per_page": 50,
  1205. }
  1206. response = self.do_request(query)
  1207. assert response.status_code == 200, response.content
  1208. assert len(response.data["data"]) == 1
  1209. data = response.data["data"]
  1210. meta = response.data["meta"]
  1211. assert data[0]["transaction"] == "foo_transaction"
  1212. assert data[0]["environment"] is None
  1213. assert data[0]["p50(transaction.duration)"] == 100
  1214. assert meta["isMetricsData"]
  1215. def test_has_transaction(self):
  1216. self.store_transaction_metric(
  1217. 1,
  1218. tags={},
  1219. timestamp=self.min_ago,
  1220. )
  1221. self.store_transaction_metric(
  1222. 100,
  1223. tags={"transaction": "foo_transaction"},
  1224. timestamp=self.min_ago,
  1225. )
  1226. query = {
  1227. "project": [self.project.id],
  1228. "orderby": "p50(transaction.duration)",
  1229. "field": [
  1230. "transaction",
  1231. "p50(transaction.duration)",
  1232. ],
  1233. "query": "has:transaction",
  1234. "statsPeriod": "24h",
  1235. "dataset": "metricsEnhanced",
  1236. "per_page": 50,
  1237. }
  1238. response = self.do_request(query)
  1239. assert response.status_code == 200, response.content
  1240. assert len(response.data["data"]) == 1
  1241. data = response.data["data"]
  1242. meta = response.data["meta"]
  1243. assert data[0]["transaction"] == "foo_transaction"
  1244. assert data[0]["p50(transaction.duration)"] == 100
  1245. assert meta["isMetricsData"]
  1246. query = {
  1247. "project": [self.project.id],
  1248. "orderby": "p50(transaction.duration)",
  1249. "field": [
  1250. "transaction",
  1251. "p50(transaction.duration)",
  1252. ],
  1253. "query": "!has:transaction",
  1254. "statsPeriod": "24h",
  1255. "dataset": "metricsEnhanced",
  1256. "per_page": 50,
  1257. }
  1258. response = self.do_request(query)
  1259. assert response.status_code == 200, response.content
  1260. assert len(response.data["data"]) == 1
  1261. data = response.data["data"]
  1262. meta = response.data["meta"]
  1263. assert data[0]["transaction"] is None
  1264. assert data[0]["p50(transaction.duration)"] == 1
  1265. assert meta["isMetricsData"]
  1266. def test_apdex_transaction_threshold(self):
  1267. ProjectTransactionThresholdOverride.objects.create(
  1268. transaction="foo_transaction",
  1269. project=self.project,
  1270. organization=self.project.organization,
  1271. threshold=600,
  1272. metric=TransactionMetric.LCP.value,
  1273. )
  1274. ProjectTransactionThresholdOverride.objects.create(
  1275. transaction="bar_transaction",
  1276. project=self.project,
  1277. organization=self.project.organization,
  1278. threshold=600,
  1279. metric=TransactionMetric.LCP.value,
  1280. )
  1281. self.store_transaction_metric(
  1282. 1,
  1283. tags={
  1284. "transaction": "foo_transaction",
  1285. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1286. },
  1287. timestamp=self.min_ago,
  1288. )
  1289. self.store_transaction_metric(
  1290. 1,
  1291. "measurements.lcp",
  1292. tags={
  1293. "transaction": "bar_transaction",
  1294. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1295. },
  1296. timestamp=self.min_ago,
  1297. )
  1298. response = self.do_request(
  1299. {
  1300. "field": [
  1301. "transaction",
  1302. "apdex()",
  1303. ],
  1304. "orderby": ["apdex()"],
  1305. "query": "event.type:transaction",
  1306. "dataset": "metrics",
  1307. "per_page": 50,
  1308. }
  1309. )
  1310. assert len(response.data["data"]) == 2
  1311. data = response.data["data"]
  1312. meta = response.data["meta"]
  1313. field_meta = meta["fields"]
  1314. assert data[0]["transaction"] == "bar_transaction"
  1315. # Threshold is lcp based
  1316. assert data[0]["apdex()"] == 1
  1317. assert data[1]["transaction"] == "foo_transaction"
  1318. # Threshold is lcp based
  1319. assert data[1]["apdex()"] == 0
  1320. assert meta["isMetricsData"]
  1321. assert field_meta["transaction"] == "string"
  1322. assert field_meta["apdex()"] == "number"
  1323. def test_apdex_satisfaction_param(self):
  1324. for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
  1325. query = {
  1326. "project": [self.project.id],
  1327. "field": [
  1328. "transaction",
  1329. function,
  1330. ],
  1331. "statsPeriod": "24h",
  1332. "dataset": "metricsEnhanced",
  1333. "per_page": 50,
  1334. }
  1335. response = self.do_request(query)
  1336. assert response.status_code == 200, response.content
  1337. assert len(response.data["data"]) == 0
  1338. meta = response.data["meta"]
  1339. assert not meta["isMetricsData"], function
  1340. query = {
  1341. "project": [self.project.id],
  1342. "field": [
  1343. "transaction",
  1344. function,
  1345. ],
  1346. "statsPeriod": "24h",
  1347. "dataset": "metrics",
  1348. "per_page": 50,
  1349. }
  1350. response = self.do_request(query)
  1351. assert response.status_code == 400, function
  1352. assert b"threshold parameter" in response.content, function