test_organization_events_mep.py 51 KB

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