test_organization_events_mep.py 124 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525
  1. from typing import Any
  2. from unittest import mock
  3. import pytest
  4. from django.urls import reverse
  5. from rest_framework.response import Response
  6. from sentry.discover.models import TeamKeyTransaction
  7. from sentry.models.dashboard_widget import DashboardWidgetTypes
  8. from sentry.models.projectteam import ProjectTeam
  9. from sentry.models.transaction_threshold import (
  10. ProjectTransactionThreshold,
  11. ProjectTransactionThresholdOverride,
  12. TransactionMetric,
  13. )
  14. from sentry.search.events import constants
  15. from sentry.search.utils import map_device_class_level
  16. from sentry.snuba.metrics.extraction import (
  17. SPEC_VERSION_TWO_FLAG,
  18. MetricSpecType,
  19. OnDemandMetricSpec,
  20. OnDemandMetricSpecVersioning,
  21. )
  22. from sentry.snuba.metrics.naming_layer.mri import TransactionMRI
  23. from sentry.snuba.metrics.naming_layer.public import TransactionMetricKey
  24. from sentry.snuba.utils import DATASET_OPTIONS
  25. from sentry.testutils.cases import MetricsEnhancedPerformanceTestCase
  26. from sentry.testutils.helpers.datetime import before_now, iso_format
  27. from sentry.testutils.helpers.discover import user_misery_formula
  28. from sentry.testutils.helpers.on_demand import create_widget
  29. from sentry.testutils.silo import region_silo_test
  30. from sentry.utils.samples import load_data
  31. pytestmark = pytest.mark.sentry_metrics
  32. @region_silo_test
  33. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  34. viewname = "sentry-api-0-organization-events"
  35. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  36. METRIC_STRINGS = [
  37. "foo_transaction",
  38. "bar_transaction",
  39. "baz_transaction",
  40. "staging",
  41. "measurement_rating",
  42. "good",
  43. "meh",
  44. "d:transactions/measurements.something_custom@millisecond",
  45. "d:transactions/measurements.runtime@hour",
  46. "d:transactions/measurements.bytes_transfered@byte",
  47. "d:transactions/measurements.datacenter_memory@petabyte",
  48. "d:transactions/measurements.custom.kilobyte@kilobyte",
  49. "d:transactions/measurements.longtaskcount@none",
  50. "d:transactions/measurements.percent@ratio",
  51. "d:transactions/measurements.custom_type@somethingcustom",
  52. ]
  53. def setUp(self):
  54. super().setUp()
  55. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  56. self.features = {
  57. "organizations:performance-use-metrics": True,
  58. }
  59. def do_request(self, query, features=None):
  60. if features is None:
  61. features = {"organizations:discover-basic": True}
  62. features.update(self.features)
  63. self.login_as(user=self.user)
  64. url = reverse(
  65. self.viewname,
  66. kwargs={"organization_slug": self.organization.slug},
  67. )
  68. with self.feature(features):
  69. return self.client.get(url, query, format="json")
  70. def test_no_projects(self):
  71. response = self.do_request(
  72. {
  73. "dataset": "metricsEnhanced",
  74. }
  75. )
  76. assert response.status_code == 200, response.content
  77. def test_invalid_dataset(self):
  78. response = self.do_request(
  79. {
  80. "dataset": "aFakeDataset",
  81. "project": self.project.id,
  82. }
  83. )
  84. assert response.status_code == 400, response.content
  85. assert (
  86. response.data["detail"]
  87. == f"dataset must be one of: {', '.join([key for key in DATASET_OPTIONS.keys()])}"
  88. )
  89. def test_out_of_retention(self):
  90. self.create_project()
  91. with self.options({"system.event-retention-days": 10}):
  92. query = {
  93. "field": ["id", "timestamp"],
  94. "orderby": ["-timestamp", "-id"],
  95. "query": "event.type:transaction",
  96. "start": iso_format(before_now(days=20)),
  97. "end": iso_format(before_now(days=15)),
  98. "dataset": "metricsEnhanced",
  99. }
  100. response = self.do_request(query)
  101. assert response.status_code == 400, response.content
  102. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  103. def test_invalid_search_terms(self):
  104. response = self.do_request(
  105. {
  106. "field": ["epm()"],
  107. "query": "hi \n there",
  108. "project": self.project.id,
  109. "dataset": "metricsEnhanced",
  110. }
  111. )
  112. assert response.status_code == 400, response.content
  113. assert (
  114. response.data["detail"]
  115. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  116. )
  117. def test_percentile_with_no_data(self):
  118. response = self.do_request(
  119. {
  120. "field": ["p50()"],
  121. "query": "",
  122. "project": self.project.id,
  123. "dataset": "metricsEnhanced",
  124. }
  125. )
  126. assert response.status_code == 200, response.content
  127. data = response.data["data"]
  128. assert len(data) == 1
  129. assert data[0]["p50()"] == 0
  130. def test_project_name(self):
  131. self.store_transaction_metric(
  132. 1,
  133. tags={"environment": "staging"},
  134. timestamp=self.min_ago,
  135. )
  136. response = self.do_request(
  137. {
  138. "field": ["project.name", "environment", "epm()"],
  139. "query": "event.type:transaction",
  140. "dataset": "metricsEnhanced",
  141. "per_page": 50,
  142. }
  143. )
  144. assert response.status_code == 200, response.content
  145. assert len(response.data["data"]) == 1
  146. data = response.data["data"]
  147. meta = response.data["meta"]
  148. field_meta = meta["fields"]
  149. assert data[0]["project.name"] == self.project.slug
  150. assert "project.id" not in data[0]
  151. assert data[0]["environment"] == "staging"
  152. assert meta["isMetricsData"]
  153. assert field_meta["project.name"] == "string"
  154. assert field_meta["environment"] == "string"
  155. assert field_meta["epm()"] == "rate"
  156. def test_project_id(self):
  157. self.store_transaction_metric(
  158. 1,
  159. tags={"environment": "staging"},
  160. timestamp=self.min_ago,
  161. )
  162. response = self.do_request(
  163. {
  164. "field": ["project_id", "environment", "epm()"],
  165. "query": "event.type:transaction",
  166. "dataset": "metrics",
  167. "per_page": 50,
  168. }
  169. )
  170. assert response.status_code == 200, response.content
  171. assert len(response.data["data"]) == 1
  172. data = response.data["data"]
  173. meta = response.data["meta"]
  174. field_meta = meta["fields"]
  175. assert data[0]["project_id"] == self.project.id
  176. assert data[0]["environment"] == "staging"
  177. assert meta["isMetricsData"]
  178. assert field_meta["project_id"] == "integer"
  179. assert field_meta["environment"] == "string"
  180. assert field_meta["epm()"] == "rate"
  181. def test_project_dot_id(self):
  182. self.store_transaction_metric(
  183. 1,
  184. tags={"environment": "staging"},
  185. timestamp=self.min_ago,
  186. )
  187. response = self.do_request(
  188. {
  189. "field": ["project.id", "environment", "epm()"],
  190. "query": "event.type:transaction",
  191. "dataset": "metrics",
  192. "per_page": 50,
  193. }
  194. )
  195. assert response.status_code == 200, response.content
  196. assert len(response.data["data"]) == 1
  197. data = response.data["data"]
  198. meta = response.data["meta"]
  199. field_meta = meta["fields"]
  200. assert data[0]["project.id"] == self.project.id
  201. assert data[0]["environment"] == "staging"
  202. assert meta["isMetricsData"]
  203. assert field_meta["project.id"] == "integer"
  204. assert field_meta["environment"] == "string"
  205. assert field_meta["epm()"] == "rate"
  206. def test_title_alias(self):
  207. """title is an alias to transaction name"""
  208. self.store_transaction_metric(
  209. 1,
  210. tags={"transaction": "foo_transaction"},
  211. timestamp=self.min_ago,
  212. )
  213. response = self.do_request(
  214. {
  215. "field": ["title", "p50()"],
  216. "query": "event.type:transaction",
  217. "dataset": "metricsEnhanced",
  218. "per_page": 50,
  219. }
  220. )
  221. assert response.status_code == 200, response.content
  222. assert len(response.data["data"]) == 1
  223. data = response.data["data"]
  224. meta = response.data["meta"]
  225. field_meta = meta["fields"]
  226. assert data[0]["title"] == "foo_transaction"
  227. assert data[0]["p50()"] == 1
  228. assert meta["isMetricsData"]
  229. assert field_meta["title"] == "string"
  230. assert field_meta["p50()"] == "duration"
  231. def test_having_condition(self):
  232. self.store_transaction_metric(
  233. 1,
  234. tags={"environment": "staging", "transaction": "foo_transaction"},
  235. timestamp=self.min_ago,
  236. )
  237. self.store_transaction_metric(
  238. # shouldn't show up
  239. 100,
  240. tags={"environment": "staging", "transaction": "bar_transaction"},
  241. timestamp=self.min_ago,
  242. )
  243. response = self.do_request(
  244. {
  245. "field": ["transaction", "project", "p50(transaction.duration)"],
  246. "query": "event.type:transaction p50(transaction.duration):<50",
  247. "dataset": "metricsEnhanced",
  248. "per_page": 50,
  249. }
  250. )
  251. assert response.status_code == 200, response.content
  252. assert len(response.data["data"]) == 1
  253. data = response.data["data"]
  254. meta = response.data["meta"]
  255. field_meta = meta["fields"]
  256. assert data[0]["transaction"] == "foo_transaction"
  257. assert data[0]["project"] == self.project.slug
  258. assert data[0]["p50(transaction.duration)"] == 1
  259. assert meta["isMetricsData"]
  260. assert field_meta["transaction"] == "string"
  261. assert field_meta["project"] == "string"
  262. assert field_meta["p50(transaction.duration)"] == "duration"
  263. def test_having_condition_with_preventing_aggregates(self):
  264. self.store_transaction_metric(
  265. 1,
  266. tags={"environment": "staging", "transaction": "foo_transaction"},
  267. timestamp=self.min_ago,
  268. )
  269. self.store_transaction_metric(
  270. 100,
  271. tags={"environment": "staging", "transaction": "bar_transaction"},
  272. timestamp=self.min_ago,
  273. )
  274. response = self.do_request(
  275. {
  276. "field": ["transaction", "project", "p50(transaction.duration)"],
  277. "query": "event.type:transaction p50(transaction.duration):<50",
  278. "dataset": "metricsEnhanced",
  279. "preventMetricAggregates": "1",
  280. "per_page": 50,
  281. }
  282. )
  283. assert response.status_code == 200, response.content
  284. assert len(response.data["data"]) == 0
  285. meta = response.data["meta"]
  286. field_meta = meta["fields"]
  287. assert not meta["isMetricsData"]
  288. assert field_meta["transaction"] == "string"
  289. assert field_meta["project"] == "string"
  290. assert field_meta["p50(transaction.duration)"] == "duration"
  291. def test_having_condition_with_preventing_aggregate_metrics_only(self):
  292. """same as the previous test, but with the dataset on explicit metrics
  293. which should throw a 400 error instead"""
  294. response = self.do_request(
  295. {
  296. "field": ["transaction", "project", "p50(transaction.duration)"],
  297. "query": "event.type:transaction p50(transaction.duration):<50",
  298. "dataset": "metrics",
  299. "preventMetricAggregates": "1",
  300. "per_page": 50,
  301. "project": self.project.id,
  302. }
  303. )
  304. assert response.status_code == 400, response.content
  305. def test_having_condition_not_selected(self):
  306. self.store_transaction_metric(
  307. 1,
  308. tags={"environment": "staging", "transaction": "foo_transaction"},
  309. timestamp=self.min_ago,
  310. )
  311. self.store_transaction_metric(
  312. # shouldn't show up
  313. 100,
  314. tags={"environment": "staging", "transaction": "bar_transaction"},
  315. timestamp=self.min_ago,
  316. )
  317. response = self.do_request(
  318. {
  319. "field": ["transaction", "project", "p50(transaction.duration)"],
  320. "query": "event.type:transaction p75(transaction.duration):<50",
  321. "dataset": "metricsEnhanced",
  322. "per_page": 50,
  323. }
  324. )
  325. assert response.status_code == 200, response.content
  326. assert len(response.data["data"]) == 1
  327. data = response.data["data"]
  328. meta = response.data["meta"]
  329. field_meta = meta["fields"]
  330. assert data[0]["transaction"] == "foo_transaction"
  331. assert data[0]["project"] == self.project.slug
  332. assert data[0]["p50(transaction.duration)"] == 1
  333. assert meta["isMetricsData"]
  334. assert field_meta["transaction"] == "string"
  335. assert field_meta["project"] == "string"
  336. assert field_meta["p50(transaction.duration)"] == "duration"
  337. def test_non_metrics_tag_with_implicit_format(self):
  338. self.store_transaction_metric(
  339. 1,
  340. tags={"environment": "staging", "transaction": "foo_transaction"},
  341. timestamp=self.min_ago,
  342. )
  343. response = self.do_request(
  344. {
  345. "field": ["test", "p50(transaction.duration)"],
  346. "query": "event.type:transaction",
  347. "dataset": "metricsEnhanced",
  348. "per_page": 50,
  349. }
  350. )
  351. assert response.status_code == 200, response.content
  352. assert len(response.data["data"]) == 0
  353. assert not response.data["meta"]["isMetricsData"]
  354. def test_non_metrics_tag_with_implicit_format_metrics_dataset(self):
  355. self.store_transaction_metric(
  356. 1,
  357. tags={"environment": "staging", "transaction": "foo_transaction"},
  358. timestamp=self.min_ago,
  359. )
  360. response = self.do_request(
  361. {
  362. "field": ["test", "p50(transaction.duration)"],
  363. "query": "event.type:transaction",
  364. "dataset": "metrics",
  365. "per_page": 50,
  366. }
  367. )
  368. assert response.status_code == 400, response.content
  369. def test_performance_homepage_query(self):
  370. self.store_transaction_metric(
  371. 1,
  372. tags={
  373. "transaction": "foo_transaction",
  374. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  375. },
  376. timestamp=self.min_ago,
  377. )
  378. self.store_transaction_metric(
  379. 1,
  380. "measurements.fcp",
  381. tags={"transaction": "foo_transaction"},
  382. timestamp=self.min_ago,
  383. )
  384. self.store_transaction_metric(
  385. 2,
  386. "measurements.lcp",
  387. tags={"transaction": "foo_transaction"},
  388. timestamp=self.min_ago,
  389. )
  390. self.store_transaction_metric(
  391. 3,
  392. "measurements.fid",
  393. tags={"transaction": "foo_transaction"},
  394. timestamp=self.min_ago,
  395. )
  396. self.store_transaction_metric(
  397. 4,
  398. "measurements.cls",
  399. tags={"transaction": "foo_transaction"},
  400. timestamp=self.min_ago,
  401. )
  402. self.store_transaction_metric(
  403. 1,
  404. "user",
  405. tags={
  406. "transaction": "foo_transaction",
  407. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
  408. },
  409. timestamp=self.min_ago,
  410. )
  411. for dataset in ["metrics", "metricsEnhanced"]:
  412. response = self.do_request(
  413. {
  414. "field": [
  415. "transaction",
  416. "project",
  417. "tpm()",
  418. "p75(measurements.fcp)",
  419. "p75(measurements.lcp)",
  420. "p75(measurements.fid)",
  421. "p75(measurements.cls)",
  422. "count_unique(user)",
  423. "apdex()",
  424. "count_miserable(user)",
  425. "user_misery()",
  426. "failure_rate()",
  427. "failure_count()",
  428. ],
  429. "orderby": "tpm()",
  430. "query": "event.type:transaction",
  431. "dataset": dataset,
  432. "per_page": 50,
  433. }
  434. )
  435. assert len(response.data["data"]) == 1
  436. data = response.data["data"][0]
  437. meta = response.data["meta"]
  438. field_meta = meta["fields"]
  439. assert data["transaction"] == "foo_transaction"
  440. assert data["project"] == self.project.slug
  441. assert data["p75(measurements.fcp)"] == 1.0
  442. assert data["p75(measurements.lcp)"] == 2.0
  443. assert data["p75(measurements.fid)"] == 3.0
  444. assert data["p75(measurements.cls)"] == 4.0
  445. assert data["apdex()"] == 1.0
  446. assert data["count_miserable(user)"] == 1.0
  447. assert data["user_misery()"] == 0.058
  448. assert data["failure_rate()"] == 1
  449. assert data["failure_count()"] == 1
  450. assert meta["isMetricsData"]
  451. assert field_meta["transaction"] == "string"
  452. assert field_meta["project"] == "string"
  453. assert field_meta["p75(measurements.fcp)"] == "duration"
  454. assert field_meta["p75(measurements.lcp)"] == "duration"
  455. assert field_meta["p75(measurements.fid)"] == "duration"
  456. assert field_meta["p75(measurements.cls)"] == "number"
  457. assert field_meta["apdex()"] == "number"
  458. assert field_meta["count_miserable(user)"] == "integer"
  459. assert field_meta["user_misery()"] == "number"
  460. assert field_meta["failure_rate()"] == "percentage"
  461. assert field_meta["failure_count()"] == "integer"
  462. assert field_meta["tpm()"] == "rate"
  463. assert meta["units"]["tpm()"] == "1/minute"
  464. def test_user_misery_and_team_key_sort(self):
  465. self.store_transaction_metric(
  466. 1,
  467. tags={
  468. "transaction": "foo_transaction",
  469. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  470. },
  471. timestamp=self.min_ago,
  472. )
  473. self.store_transaction_metric(
  474. 1,
  475. "measurements.fcp",
  476. tags={"transaction": "foo_transaction"},
  477. timestamp=self.min_ago,
  478. )
  479. self.store_transaction_metric(
  480. 2,
  481. "measurements.lcp",
  482. tags={"transaction": "foo_transaction"},
  483. timestamp=self.min_ago,
  484. )
  485. self.store_transaction_metric(
  486. 3,
  487. "measurements.fid",
  488. tags={"transaction": "foo_transaction"},
  489. timestamp=self.min_ago,
  490. )
  491. self.store_transaction_metric(
  492. 4,
  493. "measurements.cls",
  494. tags={"transaction": "foo_transaction"},
  495. timestamp=self.min_ago,
  496. )
  497. self.store_transaction_metric(
  498. 1,
  499. "user",
  500. tags={
  501. "transaction": "foo_transaction",
  502. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
  503. },
  504. timestamp=self.min_ago,
  505. )
  506. response = self.do_request(
  507. {
  508. "field": [
  509. "team_key_transaction",
  510. "transaction",
  511. "project",
  512. "tpm()",
  513. "p75(measurements.fcp)",
  514. "p75(measurements.lcp)",
  515. "p75(measurements.fid)",
  516. "p75(measurements.cls)",
  517. "count_unique(user)",
  518. "apdex()",
  519. "count_miserable(user)",
  520. "user_misery()",
  521. "failure_rate()",
  522. "failure_count()",
  523. ],
  524. "orderby": ["team_key_transaction", "user_misery()"],
  525. "query": "event.type:transaction",
  526. "dataset": "metrics",
  527. "per_page": 50,
  528. }
  529. )
  530. assert response.status_code == 200, response.content
  531. assert len(response.data["data"]) == 1
  532. data = response.data["data"][0]
  533. meta = response.data["meta"]
  534. field_meta = meta["fields"]
  535. assert data["transaction"] == "foo_transaction"
  536. assert data["project"] == self.project.slug
  537. assert data["p75(measurements.fcp)"] == 1.0
  538. assert data["p75(measurements.lcp)"] == 2.0
  539. assert data["p75(measurements.fid)"] == 3.0
  540. assert data["p75(measurements.cls)"] == 4.0
  541. assert data["apdex()"] == 1.0
  542. assert data["count_miserable(user)"] == 1.0
  543. assert data["user_misery()"] == 0.058
  544. assert data["failure_rate()"] == 1
  545. assert data["failure_count()"] == 1
  546. assert meta["isMetricsData"]
  547. assert field_meta["transaction"] == "string"
  548. assert field_meta["project"] == "string"
  549. assert field_meta["p75(measurements.fcp)"] == "duration"
  550. assert field_meta["p75(measurements.lcp)"] == "duration"
  551. assert field_meta["p75(measurements.fid)"] == "duration"
  552. assert field_meta["p75(measurements.cls)"] == "number"
  553. assert field_meta["apdex()"] == "number"
  554. assert field_meta["count_miserable(user)"] == "integer"
  555. assert field_meta["user_misery()"] == "number"
  556. assert field_meta["failure_rate()"] == "percentage"
  557. assert field_meta["failure_count()"] == "integer"
  558. def test_no_team_key_transactions(self):
  559. self.store_transaction_metric(
  560. 1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago
  561. )
  562. self.store_transaction_metric(
  563. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  564. )
  565. query = {
  566. "team": "myteams",
  567. "project": [self.project.id],
  568. # TODO sort by transaction here once that's possible for order to match the same test without metrics
  569. "orderby": "p95()",
  570. "field": [
  571. "team_key_transaction",
  572. "transaction",
  573. "transaction.status",
  574. "project",
  575. "epm()",
  576. "failure_rate()",
  577. "p95()",
  578. ],
  579. "per_page": 50,
  580. "dataset": "metricsEnhanced",
  581. }
  582. response = self.do_request(query)
  583. assert response.status_code == 200, response.content
  584. assert len(response.data["data"]) == 2
  585. data = response.data["data"]
  586. meta = response.data["meta"]
  587. field_meta = meta["fields"]
  588. assert data[0]["team_key_transaction"] == 0
  589. assert data[0]["transaction"] == "foo_transaction"
  590. assert data[1]["team_key_transaction"] == 0
  591. assert data[1]["transaction"] == "bar_transaction"
  592. assert meta["isMetricsData"]
  593. assert field_meta["team_key_transaction"] == "boolean"
  594. assert field_meta["transaction"] == "string"
  595. def test_team_key_transactions_my_teams(self):
  596. team1 = self.create_team(organization=self.organization, name="Team A")
  597. self.create_team_membership(team1, user=self.user)
  598. self.project.add_team(team1)
  599. team2 = self.create_team(organization=self.organization, name="Team B")
  600. self.project.add_team(team2)
  601. key_transactions = [
  602. (team1, "foo_transaction"),
  603. (team2, "baz_transaction"),
  604. ]
  605. # Not a key transaction
  606. self.store_transaction_metric(
  607. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  608. )
  609. for team, transaction in key_transactions:
  610. self.store_transaction_metric(
  611. 1, tags={"transaction": transaction}, timestamp=self.min_ago
  612. )
  613. TeamKeyTransaction.objects.create(
  614. organization=self.organization,
  615. transaction=transaction,
  616. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  617. )
  618. query = {
  619. "team": "myteams",
  620. "project": [self.project.id],
  621. "field": [
  622. "team_key_transaction",
  623. "transaction",
  624. "transaction.status",
  625. "project",
  626. "epm()",
  627. "failure_rate()",
  628. "p95()",
  629. ],
  630. "per_page": 50,
  631. "dataset": "metricsEnhanced",
  632. }
  633. query["orderby"] = ["team_key_transaction", "p95()"]
  634. response = self.do_request(query)
  635. assert response.status_code == 200, response.content
  636. assert len(response.data["data"]) == 3
  637. data = response.data["data"]
  638. meta = response.data["meta"]
  639. field_meta = meta["fields"]
  640. assert data[0]["team_key_transaction"] == 0
  641. assert data[0]["transaction"] == "baz_transaction"
  642. assert data[1]["team_key_transaction"] == 0
  643. assert data[1]["transaction"] == "bar_transaction"
  644. assert data[2]["team_key_transaction"] == 1
  645. assert data[2]["transaction"] == "foo_transaction"
  646. assert meta["isMetricsData"]
  647. assert field_meta["team_key_transaction"] == "boolean"
  648. assert field_meta["transaction"] == "string"
  649. # not specifying any teams should use my teams
  650. query = {
  651. "project": [self.project.id],
  652. "field": [
  653. "team_key_transaction",
  654. "transaction",
  655. "transaction.status",
  656. "project",
  657. "epm()",
  658. "failure_rate()",
  659. "p95()",
  660. ],
  661. "per_page": 50,
  662. "dataset": "metricsEnhanced",
  663. }
  664. query["orderby"] = ["team_key_transaction", "p95()"]
  665. response = self.do_request(query)
  666. assert response.status_code == 200, response.content
  667. assert len(response.data["data"]) == 3
  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"] == "baz_transaction"
  673. assert data[1]["team_key_transaction"] == 0
  674. assert data[1]["transaction"] == "bar_transaction"
  675. assert data[2]["team_key_transaction"] == 1
  676. assert data[2]["transaction"] == "foo_transaction"
  677. assert meta["isMetricsData"]
  678. assert field_meta["team_key_transaction"] == "boolean"
  679. assert field_meta["transaction"] == "string"
  680. def test_team_key_transactions_orderby(self):
  681. team1 = self.create_team(organization=self.organization, name="Team A")
  682. team2 = self.create_team(organization=self.organization, name="Team B")
  683. key_transactions = [
  684. (team1, "foo_transaction", 1),
  685. (team2, "baz_transaction", 100),
  686. ]
  687. # Not a key transaction
  688. self.store_transaction_metric(
  689. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  690. )
  691. for team, transaction, value in key_transactions:
  692. self.store_transaction_metric(
  693. value, tags={"transaction": transaction}, timestamp=self.min_ago
  694. )
  695. self.create_team_membership(team, user=self.user)
  696. self.project.add_team(team)
  697. TeamKeyTransaction.objects.create(
  698. organization=self.organization,
  699. transaction=transaction,
  700. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  701. )
  702. query = {
  703. "team": "myteams",
  704. "project": [self.project.id],
  705. "field": [
  706. "team_key_transaction",
  707. "transaction",
  708. "transaction.status",
  709. "project",
  710. "epm()",
  711. "failure_rate()",
  712. "p95()",
  713. ],
  714. "per_page": 50,
  715. "dataset": "metricsEnhanced",
  716. }
  717. # test ascending order
  718. query["orderby"] = ["team_key_transaction", "p95()"]
  719. response = self.do_request(query)
  720. assert response.status_code == 200, response.content
  721. assert len(response.data["data"]) == 3
  722. data = response.data["data"]
  723. meta = response.data["meta"]
  724. field_meta = meta["fields"]
  725. assert data[0]["team_key_transaction"] == 0
  726. assert data[0]["transaction"] == "bar_transaction"
  727. assert data[1]["team_key_transaction"] == 1
  728. assert data[1]["transaction"] == "foo_transaction"
  729. assert data[2]["team_key_transaction"] == 1
  730. assert data[2]["transaction"] == "baz_transaction"
  731. assert meta["isMetricsData"]
  732. assert field_meta["team_key_transaction"] == "boolean"
  733. assert field_meta["transaction"] == "string"
  734. # test descending order
  735. query["orderby"] = ["-team_key_transaction", "p95()"]
  736. response = self.do_request(query)
  737. assert response.status_code == 200, response.content
  738. assert len(response.data["data"]) == 3
  739. data = response.data["data"]
  740. meta = response.data["meta"]
  741. field_meta = meta["fields"]
  742. assert data[0]["team_key_transaction"] == 1
  743. assert data[0]["transaction"] == "foo_transaction"
  744. assert data[1]["team_key_transaction"] == 1
  745. assert data[1]["transaction"] == "baz_transaction"
  746. assert data[2]["team_key_transaction"] == 0
  747. assert data[2]["transaction"] == "bar_transaction"
  748. assert meta["isMetricsData"]
  749. assert field_meta["team_key_transaction"] == "boolean"
  750. assert field_meta["transaction"] == "string"
  751. def test_team_key_transactions_query(self):
  752. team1 = self.create_team(organization=self.organization, name="Team A")
  753. team2 = self.create_team(organization=self.organization, name="Team B")
  754. key_transactions = [
  755. (team1, "foo_transaction", 1),
  756. (team2, "baz_transaction", 100),
  757. ]
  758. # Not a key transaction
  759. self.store_transaction_metric(
  760. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  761. )
  762. for team, transaction, value in key_transactions:
  763. self.store_transaction_metric(
  764. value, tags={"transaction": transaction}, timestamp=self.min_ago
  765. )
  766. self.create_team_membership(team, user=self.user)
  767. self.project.add_team(team)
  768. TeamKeyTransaction.objects.create(
  769. organization=self.organization,
  770. transaction=transaction,
  771. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  772. )
  773. query = {
  774. "team": "myteams",
  775. "project": [self.project.id],
  776. # use the order by to ensure the result order
  777. "orderby": "p95()",
  778. "field": [
  779. "team_key_transaction",
  780. "transaction",
  781. "transaction.status",
  782. "project",
  783. "epm()",
  784. "failure_rate()",
  785. "p95()",
  786. ],
  787. "per_page": 50,
  788. "dataset": "metricsEnhanced",
  789. }
  790. # key transactions
  791. query["query"] = "has:team_key_transaction"
  792. response = self.do_request(query)
  793. assert response.status_code == 200, response.content
  794. assert len(response.data["data"]) == 2
  795. data = response.data["data"]
  796. meta = response.data["meta"]
  797. field_meta = meta["fields"]
  798. assert data[0]["team_key_transaction"] == 1
  799. assert data[0]["transaction"] == "foo_transaction"
  800. assert data[1]["team_key_transaction"] == 1
  801. assert data[1]["transaction"] == "baz_transaction"
  802. assert meta["isMetricsData"]
  803. assert field_meta["team_key_transaction"] == "boolean"
  804. assert field_meta["transaction"] == "string"
  805. # key transactions
  806. query["query"] = "team_key_transaction:true"
  807. response = self.do_request(query)
  808. assert response.status_code == 200, response.content
  809. assert len(response.data["data"]) == 2
  810. data = response.data["data"]
  811. meta = response.data["meta"]
  812. field_meta = meta["fields"]
  813. assert data[0]["team_key_transaction"] == 1
  814. assert data[0]["transaction"] == "foo_transaction"
  815. assert data[1]["team_key_transaction"] == 1
  816. assert data[1]["transaction"] == "baz_transaction"
  817. assert meta["isMetricsData"]
  818. assert field_meta["team_key_transaction"] == "boolean"
  819. assert field_meta["transaction"] == "string"
  820. # not key transactions
  821. query["query"] = "!has:team_key_transaction"
  822. response = self.do_request(query)
  823. assert response.status_code == 200, response.content
  824. assert len(response.data["data"]) == 1
  825. data = response.data["data"]
  826. meta = response.data["meta"]
  827. field_meta = meta["fields"]
  828. assert data[0]["team_key_transaction"] == 0
  829. assert data[0]["transaction"] == "bar_transaction"
  830. assert meta["isMetricsData"]
  831. assert field_meta["team_key_transaction"] == "boolean"
  832. assert field_meta["transaction"] == "string"
  833. # not key transactions
  834. query["query"] = "team_key_transaction:false"
  835. response = self.do_request(query)
  836. assert response.status_code == 200, response.content
  837. assert len(response.data["data"]) == 1
  838. data = response.data["data"]
  839. meta = response.data["meta"]
  840. field_meta = meta["fields"]
  841. assert data[0]["team_key_transaction"] == 0
  842. assert data[0]["transaction"] == "bar_transaction"
  843. assert meta["isMetricsData"]
  844. assert field_meta["team_key_transaction"] == "boolean"
  845. assert field_meta["transaction"] == "string"
  846. def test_team_key_transaction_not_exists(self):
  847. team1 = self.create_team(organization=self.organization, name="Team A")
  848. team2 = self.create_team(organization=self.organization, name="Team B")
  849. key_transactions = [
  850. (team1, "foo_transaction", 1),
  851. (team2, "baz_transaction", 100),
  852. ]
  853. for team, transaction, value in key_transactions:
  854. self.store_transaction_metric(
  855. value, tags={"transaction": transaction}, timestamp=self.min_ago
  856. )
  857. self.create_team_membership(team, user=self.user)
  858. self.project.add_team(team)
  859. TeamKeyTransaction.objects.create(
  860. organization=self.organization,
  861. transaction=transaction,
  862. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  863. )
  864. # Don't create a metric for this one
  865. TeamKeyTransaction.objects.create(
  866. organization=self.organization,
  867. transaction="not_in_metrics",
  868. project_team=ProjectTeam.objects.get(project=self.project, team=team1),
  869. )
  870. query = {
  871. "team": "myteams",
  872. "project": [self.project.id],
  873. # use the order by to ensure the result order
  874. "orderby": "p95()",
  875. "field": [
  876. "team_key_transaction",
  877. "transaction",
  878. "transaction.status",
  879. "project",
  880. "epm()",
  881. "failure_rate()",
  882. "p95()",
  883. ],
  884. "per_page": 50,
  885. "dataset": "metricsEnhanced",
  886. }
  887. # key transactions
  888. query["query"] = "has:team_key_transaction"
  889. response = self.do_request(query)
  890. assert response.status_code == 200, response.content
  891. assert len(response.data["data"]) == 2
  892. data = response.data["data"]
  893. meta = response.data["meta"]
  894. field_meta = meta["fields"]
  895. assert data[0]["team_key_transaction"] == 1
  896. assert data[0]["transaction"] == "foo_transaction"
  897. assert data[1]["team_key_transaction"] == 1
  898. assert data[1]["transaction"] == "baz_transaction"
  899. assert meta["isMetricsData"]
  900. assert field_meta["team_key_transaction"] == "boolean"
  901. assert field_meta["transaction"] == "string"
  902. # key transactions
  903. query["query"] = "team_key_transaction:true"
  904. response = self.do_request(query)
  905. assert response.status_code == 200, response.content
  906. assert len(response.data["data"]) == 2
  907. data = response.data["data"]
  908. meta = response.data["meta"]
  909. field_meta = meta["fields"]
  910. assert data[0]["team_key_transaction"] == 1
  911. assert data[0]["transaction"] == "foo_transaction"
  912. assert data[1]["team_key_transaction"] == 1
  913. assert data[1]["transaction"] == "baz_transaction"
  914. assert meta["isMetricsData"]
  915. assert field_meta["team_key_transaction"] == "boolean"
  916. assert field_meta["transaction"] == "string"
  917. # not key transactions
  918. query["query"] = "!has:team_key_transaction"
  919. response = self.do_request(query)
  920. assert response.status_code == 200, response.content
  921. assert len(response.data["data"]) == 0
  922. data = response.data["data"]
  923. meta = response.data["meta"]
  924. field_meta = meta["fields"]
  925. assert meta["isMetricsData"]
  926. assert field_meta["team_key_transaction"] == "boolean"
  927. assert field_meta["transaction"] == "string"
  928. # not key transactions
  929. query["query"] = "team_key_transaction:false"
  930. response = self.do_request(query)
  931. assert response.status_code == 200, response.content
  932. assert len(response.data["data"]) == 0
  933. data = response.data["data"]
  934. meta = response.data["meta"]
  935. field_meta = meta["fields"]
  936. assert meta["isMetricsData"]
  937. assert field_meta["team_key_transaction"] == "boolean"
  938. assert field_meta["transaction"] == "string"
  939. def test_too_many_team_key_transactions(self):
  940. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  941. with mock.patch(
  942. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  943. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  944. ):
  945. team = self.create_team(organization=self.organization, name="Team A")
  946. self.create_team_membership(team, user=self.user)
  947. self.project.add_team(team)
  948. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  949. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  950. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  951. self.store_transaction_metric(
  952. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  953. )
  954. TeamKeyTransaction.objects.bulk_create(
  955. [
  956. TeamKeyTransaction(
  957. organization=self.organization,
  958. project_team=project_team,
  959. transaction=transactions[i],
  960. )
  961. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  962. ]
  963. )
  964. query = {
  965. "team": "myteams",
  966. "project": [self.project.id],
  967. "orderby": "p95()",
  968. "field": [
  969. "team_key_transaction",
  970. "transaction",
  971. "transaction.status",
  972. "project",
  973. "epm()",
  974. "failure_rate()",
  975. "p95()",
  976. ],
  977. "dataset": "metricsEnhanced",
  978. "per_page": 50,
  979. }
  980. response = self.do_request(query)
  981. assert response.status_code == 200, response.content
  982. assert len(response.data["data"]) == 2
  983. data = response.data["data"]
  984. meta = response.data["meta"]
  985. assert (
  986. sum(row["team_key_transaction"] for row in data)
  987. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  988. )
  989. assert meta["isMetricsData"]
  990. def test_measurement_rating(self):
  991. self.store_transaction_metric(
  992. 50,
  993. metric="measurements.lcp",
  994. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  995. timestamp=self.min_ago,
  996. )
  997. self.store_transaction_metric(
  998. 15,
  999. metric="measurements.fp",
  1000. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  1001. timestamp=self.min_ago,
  1002. )
  1003. self.store_transaction_metric(
  1004. 1500,
  1005. metric="measurements.fcp",
  1006. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  1007. timestamp=self.min_ago,
  1008. )
  1009. self.store_transaction_metric(
  1010. 125,
  1011. metric="measurements.fid",
  1012. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  1013. timestamp=self.min_ago,
  1014. )
  1015. self.store_transaction_metric(
  1016. 0.15,
  1017. metric="measurements.cls",
  1018. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  1019. timestamp=self.min_ago,
  1020. )
  1021. response = self.do_request(
  1022. {
  1023. "field": [
  1024. "transaction",
  1025. "count_web_vitals(measurements.lcp, good)",
  1026. "count_web_vitals(measurements.fp, good)",
  1027. "count_web_vitals(measurements.fcp, meh)",
  1028. "count_web_vitals(measurements.fid, meh)",
  1029. "count_web_vitals(measurements.cls, good)",
  1030. "count_web_vitals(measurements.lcp, any)",
  1031. ],
  1032. "query": "event.type:transaction",
  1033. "dataset": "metricsEnhanced",
  1034. "per_page": 50,
  1035. }
  1036. )
  1037. assert response.status_code == 200, response.content
  1038. assert len(response.data["data"]) == 1
  1039. data = response.data["data"]
  1040. meta = response.data["meta"]
  1041. field_meta = meta["fields"]
  1042. assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
  1043. assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
  1044. assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
  1045. assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
  1046. assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
  1047. assert data[0]["count_web_vitals(measurements.lcp, any)"] == 1
  1048. assert meta["isMetricsData"]
  1049. assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
  1050. assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
  1051. assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
  1052. assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
  1053. assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
  1054. assert field_meta["count_web_vitals(measurements.lcp, any)"] == "integer"
  1055. def test_measurement_rating_that_does_not_exist(self):
  1056. self.store_transaction_metric(
  1057. 1,
  1058. metric="measurements.lcp",
  1059. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  1060. timestamp=self.min_ago,
  1061. )
  1062. response = self.do_request(
  1063. {
  1064. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  1065. "query": "event.type:transaction",
  1066. "dataset": "metricsEnhanced",
  1067. "per_page": 50,
  1068. }
  1069. )
  1070. assert response.status_code == 200, response.content
  1071. assert len(response.data["data"]) == 1
  1072. data = response.data["data"]
  1073. meta = response.data["meta"]
  1074. assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
  1075. assert meta["isMetricsData"]
  1076. assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
  1077. def test_count_web_vitals_invalid_vital(self):
  1078. query = {
  1079. "field": [
  1080. "count_web_vitals(measurements.foo, poor)",
  1081. ],
  1082. "project": [self.project.id],
  1083. "dataset": "metricsEnhanced",
  1084. }
  1085. response = self.do_request(query)
  1086. assert response.status_code == 400, response.content
  1087. query = {
  1088. "field": [
  1089. "count_web_vitals(tags[lcp], poor)",
  1090. ],
  1091. "project": [self.project.id],
  1092. "dataset": "metricsEnhanced",
  1093. }
  1094. response = self.do_request(query)
  1095. assert response.status_code == 400, response.content
  1096. query = {
  1097. "field": [
  1098. "count_web_vitals(transaction.duration, poor)",
  1099. ],
  1100. "project": [self.project.id],
  1101. "dataset": "metricsEnhanced",
  1102. }
  1103. response = self.do_request(query)
  1104. assert response.status_code == 400, response.content
  1105. query = {
  1106. "field": [
  1107. "count_web_vitals(measurements.lcp, bad)",
  1108. ],
  1109. "project": [self.project.id],
  1110. "dataset": "metricsEnhanced",
  1111. }
  1112. response = self.do_request(query)
  1113. assert response.status_code == 400, response.content
  1114. def test_count_unique_user_returns_zero(self):
  1115. self.store_transaction_metric(
  1116. 50,
  1117. metric="user",
  1118. tags={"transaction": "foo_transaction"},
  1119. timestamp=self.min_ago,
  1120. )
  1121. self.store_transaction_metric(
  1122. 50,
  1123. tags={"transaction": "foo_transaction"},
  1124. timestamp=self.min_ago,
  1125. )
  1126. self.store_transaction_metric(
  1127. 100,
  1128. tags={"transaction": "bar_transaction"},
  1129. timestamp=self.min_ago,
  1130. )
  1131. query = {
  1132. "project": [self.project.id],
  1133. "orderby": "p50()",
  1134. "field": [
  1135. "transaction",
  1136. "count_unique(user)",
  1137. "p50()",
  1138. ],
  1139. "dataset": "metricsEnhanced",
  1140. "per_page": 50,
  1141. }
  1142. response = self.do_request(query)
  1143. assert response.status_code == 200, response.content
  1144. assert len(response.data["data"]) == 2
  1145. data = response.data["data"]
  1146. meta = response.data["meta"]
  1147. assert data[0]["transaction"] == "foo_transaction"
  1148. assert data[0]["count_unique(user)"] == 1
  1149. assert data[1]["transaction"] == "bar_transaction"
  1150. assert data[1]["count_unique(user)"] == 0
  1151. assert meta["isMetricsData"]
  1152. def test_sum_transaction_duration(self):
  1153. self.store_transaction_metric(
  1154. 50,
  1155. tags={"transaction": "foo_transaction"},
  1156. timestamp=self.min_ago,
  1157. )
  1158. self.store_transaction_metric(
  1159. 100,
  1160. tags={"transaction": "foo_transaction"},
  1161. timestamp=self.min_ago,
  1162. )
  1163. self.store_transaction_metric(
  1164. 150,
  1165. tags={"transaction": "foo_transaction"},
  1166. timestamp=self.min_ago,
  1167. )
  1168. query = {
  1169. "project": [self.project.id],
  1170. "orderby": "sum(transaction.duration)",
  1171. "field": [
  1172. "transaction",
  1173. "sum(transaction.duration)",
  1174. ],
  1175. "dataset": "metricsEnhanced",
  1176. "per_page": 50,
  1177. }
  1178. response = self.do_request(query)
  1179. assert response.status_code == 200, response.content
  1180. assert len(response.data["data"]) == 1
  1181. data = response.data["data"]
  1182. meta = response.data["meta"]
  1183. assert data[0]["transaction"] == "foo_transaction"
  1184. assert data[0]["sum(transaction.duration)"] == 300
  1185. assert meta["isMetricsData"]
  1186. def test_custom_measurements_simple(self):
  1187. self.store_transaction_metric(
  1188. 1,
  1189. metric="measurements.something_custom",
  1190. internal_metric="d:transactions/measurements.something_custom@millisecond",
  1191. entity="metrics_distributions",
  1192. tags={"transaction": "foo_transaction"},
  1193. timestamp=self.min_ago,
  1194. )
  1195. query = {
  1196. "project": [self.project.id],
  1197. "orderby": "p50(measurements.something_custom)",
  1198. "field": [
  1199. "transaction",
  1200. "p50(measurements.something_custom)",
  1201. ],
  1202. "statsPeriod": "24h",
  1203. "dataset": "metricsEnhanced",
  1204. "per_page": 50,
  1205. }
  1206. self.wait_for_metric_count(
  1207. self.project,
  1208. 1,
  1209. metric="measurements.something_custom",
  1210. mri="d:transactions/measurements.something_custom@millisecond",
  1211. )
  1212. response = self.do_request(query)
  1213. assert response.status_code == 200, response.content
  1214. assert len(response.data["data"]) == 1
  1215. data = response.data["data"]
  1216. meta = response.data["meta"]
  1217. assert data[0]["transaction"] == "foo_transaction"
  1218. assert data[0]["p50(measurements.something_custom)"] == 1
  1219. assert meta["isMetricsData"]
  1220. assert meta["fields"]["p50(measurements.something_custom)"] == "duration"
  1221. assert meta["units"]["p50(measurements.something_custom)"] == "millisecond"
  1222. def test_custom_measurement_size_meta_type(self):
  1223. self.store_transaction_metric(
  1224. 100,
  1225. metric="measurements.custom_type",
  1226. internal_metric="d:transactions/measurements.custom_type@somethingcustom",
  1227. entity="metrics_distributions",
  1228. tags={"transaction": "foo_transaction"},
  1229. timestamp=self.min_ago,
  1230. )
  1231. self.store_transaction_metric(
  1232. 100,
  1233. metric="measurements.percent",
  1234. internal_metric="d:transactions/measurements.percent@ratio",
  1235. entity="metrics_distributions",
  1236. tags={"transaction": "foo_transaction"},
  1237. timestamp=self.min_ago,
  1238. )
  1239. self.store_transaction_metric(
  1240. 100,
  1241. metric="measurements.longtaskcount",
  1242. internal_metric="d:transactions/measurements.longtaskcount@none",
  1243. entity="metrics_distributions",
  1244. tags={"transaction": "foo_transaction"},
  1245. timestamp=self.min_ago,
  1246. )
  1247. query = {
  1248. "project": [self.project.id],
  1249. "orderby": "p50(measurements.longtaskcount)",
  1250. "field": [
  1251. "transaction",
  1252. "p50(measurements.longtaskcount)",
  1253. "p50(measurements.percent)",
  1254. "p50(measurements.custom_type)",
  1255. ],
  1256. "statsPeriod": "24h",
  1257. "dataset": "metricsEnhanced",
  1258. "per_page": 50,
  1259. }
  1260. response = self.do_request(query)
  1261. assert response.status_code == 200, response.content
  1262. assert len(response.data["data"]) == 1
  1263. data = response.data["data"]
  1264. meta = response.data["meta"]
  1265. assert data[0]["transaction"] == "foo_transaction"
  1266. assert data[0]["p50(measurements.longtaskcount)"] == 100
  1267. assert data[0]["p50(measurements.percent)"] == 100
  1268. assert data[0]["p50(measurements.custom_type)"] == 100
  1269. assert meta["isMetricsData"]
  1270. assert meta["fields"]["p50(measurements.longtaskcount)"] == "integer"
  1271. assert meta["units"]["p50(measurements.longtaskcount)"] is None
  1272. assert meta["fields"]["p50(measurements.percent)"] == "percentage"
  1273. assert meta["units"]["p50(measurements.percent)"] is None
  1274. assert meta["fields"]["p50(measurements.custom_type)"] == "number"
  1275. assert meta["units"]["p50(measurements.custom_type)"] is None
  1276. def test_custom_measurement_none_type(self):
  1277. self.store_transaction_metric(
  1278. 1,
  1279. metric="measurements.cls",
  1280. entity="metrics_distributions",
  1281. tags={"transaction": "foo_transaction"},
  1282. timestamp=self.min_ago,
  1283. )
  1284. query = {
  1285. "project": [self.project.id],
  1286. "orderby": "p75(measurements.cls)",
  1287. "field": [
  1288. "transaction",
  1289. "p75(measurements.cls)",
  1290. "p99(measurements.cls)",
  1291. "max(measurements.cls)",
  1292. ],
  1293. "statsPeriod": "24h",
  1294. "dataset": "metricsEnhanced",
  1295. "per_page": 50,
  1296. }
  1297. self.wait_for_metric_count(
  1298. self.project,
  1299. 1,
  1300. metric=TransactionMetricKey.MEASUREMENTS_CLS.value,
  1301. mri=TransactionMRI.MEASUREMENTS_CLS.value,
  1302. )
  1303. response = self.do_request(query)
  1304. assert response.status_code == 200, response.content
  1305. assert len(response.data["data"]) == 1
  1306. data = response.data["data"]
  1307. meta = response.data["meta"]
  1308. assert data[0]["transaction"] == "foo_transaction"
  1309. assert data[0]["p75(measurements.cls)"] == 1
  1310. assert data[0]["p99(measurements.cls)"] == 1
  1311. assert data[0]["max(measurements.cls)"] == 1
  1312. assert meta["isMetricsData"]
  1313. assert meta["fields"]["p75(measurements.cls)"] == "number"
  1314. assert meta["units"]["p75(measurements.cls)"] is None
  1315. assert meta["fields"]["p99(measurements.cls)"] == "number"
  1316. assert meta["units"]["p99(measurements.cls)"] is None
  1317. assert meta["fields"]["max(measurements.cls)"] == "number"
  1318. assert meta["units"]["max(measurements.cls)"] is None
  1319. def test_custom_measurement_duration_filtering(self):
  1320. self.store_transaction_metric(
  1321. 1,
  1322. metric="measurements.runtime",
  1323. internal_metric="d:transactions/measurements.runtime@hour",
  1324. entity="metrics_distributions",
  1325. tags={"transaction": "foo_transaction"},
  1326. timestamp=self.min_ago,
  1327. )
  1328. self.store_transaction_metric(
  1329. 180,
  1330. metric="measurements.runtime",
  1331. internal_metric="d:transactions/measurements.runtime@hour",
  1332. entity="metrics_distributions",
  1333. tags={"transaction": "bar_transaction"},
  1334. timestamp=self.min_ago,
  1335. )
  1336. query = {
  1337. "project": [self.project.id],
  1338. "field": [
  1339. "transaction",
  1340. "max(measurements.runtime)",
  1341. ],
  1342. "query": "p50(measurements.runtime):>1wk",
  1343. "statsPeriod": "24h",
  1344. "dataset": "metricsEnhanced",
  1345. "per_page": 50,
  1346. }
  1347. response = self.do_request(query)
  1348. assert response.status_code == 200, response.content
  1349. assert len(response.data["data"]) == 1
  1350. data = response.data["data"]
  1351. meta = response.data["meta"]
  1352. assert data[0]["transaction"] == "bar_transaction"
  1353. assert data[0]["max(measurements.runtime)"] == 180
  1354. assert meta["isMetricsData"]
  1355. def test_custom_measurement_size_filtering(self):
  1356. self.store_transaction_metric(
  1357. 1,
  1358. metric="measurements.datacenter_memory",
  1359. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1360. entity="metrics_distributions",
  1361. tags={"transaction": "foo_transaction"},
  1362. timestamp=self.min_ago,
  1363. )
  1364. self.store_transaction_metric(
  1365. 100,
  1366. metric="measurements.datacenter_memory",
  1367. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1368. entity="metrics_distributions",
  1369. tags={"transaction": "bar_transaction"},
  1370. timestamp=self.min_ago,
  1371. )
  1372. query = {
  1373. "project": [self.project.id],
  1374. "field": [
  1375. "transaction",
  1376. "max(measurements.datacenter_memory)",
  1377. ],
  1378. "query": "p50(measurements.datacenter_memory):>5pb",
  1379. "statsPeriod": "24h",
  1380. "dataset": "metricsEnhanced",
  1381. "per_page": 50,
  1382. }
  1383. response = self.do_request(query)
  1384. assert response.status_code == 200, response.content
  1385. assert len(response.data["data"]) == 1
  1386. data = response.data["data"]
  1387. meta = response.data["meta"]
  1388. assert data[0]["transaction"] == "bar_transaction"
  1389. assert data[0]["max(measurements.datacenter_memory)"] == 100
  1390. assert meta["units"]["max(measurements.datacenter_memory)"] == "petabyte"
  1391. assert meta["fields"]["max(measurements.datacenter_memory)"] == "size"
  1392. assert meta["isMetricsData"]
  1393. def test_has_custom_measurement(self):
  1394. self.store_transaction_metric(
  1395. 33,
  1396. metric="measurements.datacenter_memory",
  1397. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1398. entity="metrics_distributions",
  1399. tags={"transaction": "foo_transaction"},
  1400. timestamp=self.min_ago,
  1401. )
  1402. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1403. transaction_data["measurements"]["datacenter_memory"] = {
  1404. "value": 33,
  1405. "unit": "petabyte",
  1406. }
  1407. self.store_event(transaction_data, self.project.id)
  1408. measurement = "measurements.datacenter_memory"
  1409. response = self.do_request(
  1410. {
  1411. "field": ["transaction", measurement],
  1412. "query": "has:measurements.datacenter_memory",
  1413. "dataset": "discover",
  1414. }
  1415. )
  1416. assert response.status_code == 200, response.content
  1417. assert len(response.data["data"]) == 1
  1418. response = self.do_request(
  1419. {
  1420. "field": ["transaction", measurement],
  1421. "query": "!has:measurements.datacenter_memory",
  1422. "dataset": "discover",
  1423. }
  1424. )
  1425. assert response.status_code == 200, response.content
  1426. assert len(response.data["data"]) == 0
  1427. def test_environment_param(self):
  1428. self.create_environment(self.project, name="staging")
  1429. self.store_transaction_metric(
  1430. 1,
  1431. tags={"transaction": "foo_transaction", "environment": "staging"},
  1432. timestamp=self.min_ago,
  1433. )
  1434. self.store_transaction_metric(
  1435. 100,
  1436. tags={"transaction": "foo_transaction"},
  1437. timestamp=self.min_ago,
  1438. )
  1439. query = {
  1440. "project": [self.project.id],
  1441. "environment": "staging",
  1442. "orderby": "p50(transaction.duration)",
  1443. "field": [
  1444. "transaction",
  1445. "environment",
  1446. "p50(transaction.duration)",
  1447. ],
  1448. "statsPeriod": "24h",
  1449. "dataset": "metricsEnhanced",
  1450. "per_page": 50,
  1451. }
  1452. response = self.do_request(query)
  1453. assert response.status_code == 200, response.content
  1454. assert len(response.data["data"]) == 1
  1455. data = response.data["data"]
  1456. meta = response.data["meta"]
  1457. assert data[0]["transaction"] == "foo_transaction"
  1458. assert data[0]["environment"] == "staging"
  1459. assert data[0]["p50(transaction.duration)"] == 1
  1460. assert meta["isMetricsData"]
  1461. @pytest.mark.xfail(reason="Started failing on ClickHouse 21.8")
  1462. def test_environment_query(self):
  1463. self.create_environment(self.project, name="staging")
  1464. self.store_transaction_metric(
  1465. 1,
  1466. tags={"transaction": "foo_transaction", "environment": "staging"},
  1467. timestamp=self.min_ago,
  1468. )
  1469. self.store_transaction_metric(
  1470. 100,
  1471. tags={"transaction": "foo_transaction"},
  1472. timestamp=self.min_ago,
  1473. )
  1474. query = {
  1475. "project": [self.project.id],
  1476. "orderby": "p50(transaction.duration)",
  1477. "field": [
  1478. "transaction",
  1479. "environment",
  1480. "p50(transaction.duration)",
  1481. ],
  1482. "query": "!has:environment",
  1483. "statsPeriod": "24h",
  1484. "dataset": "metricsEnhanced",
  1485. "per_page": 50,
  1486. }
  1487. self.wait_for_metric_count(self.project, 2)
  1488. response = self.do_request(query)
  1489. assert response.status_code == 200, response.content
  1490. assert len(response.data["data"]) == 1
  1491. data = response.data["data"]
  1492. meta = response.data["meta"]
  1493. assert data[0]["transaction"] == "foo_transaction"
  1494. assert data[0]["environment"] is None or data[0]["environment"] == ""
  1495. assert data[0]["p50(transaction.duration)"] == 100
  1496. assert meta["isMetricsData"]
  1497. def test_has_transaction(self):
  1498. self.store_transaction_metric(
  1499. 1,
  1500. tags={},
  1501. timestamp=self.min_ago,
  1502. )
  1503. self.store_transaction_metric(
  1504. 100,
  1505. tags={"transaction": "foo_transaction"},
  1506. timestamp=self.min_ago,
  1507. )
  1508. query = {
  1509. "project": [self.project.id],
  1510. "orderby": "p50(transaction.duration)",
  1511. "field": [
  1512. "transaction",
  1513. "p50(transaction.duration)",
  1514. ],
  1515. "query": "has:transaction",
  1516. "statsPeriod": "24h",
  1517. "dataset": "metricsEnhanced",
  1518. "per_page": 50,
  1519. }
  1520. self.wait_for_metric_count(self.project, 2)
  1521. response = self.do_request(query)
  1522. assert response.status_code == 200, response.content
  1523. assert len(response.data["data"]) == 2
  1524. data = response.data["data"]
  1525. meta = response.data["meta"]
  1526. assert data[0]["transaction"] == "<< unparameterized >>"
  1527. assert data[0]["p50(transaction.duration)"] == 1
  1528. assert data[1]["transaction"] == "foo_transaction"
  1529. assert data[1]["p50(transaction.duration)"] == 100
  1530. assert meta["isMetricsData"]
  1531. query = {
  1532. "project": [self.project.id],
  1533. "orderby": "p50(transaction.duration)",
  1534. "field": [
  1535. "transaction",
  1536. "p50(transaction.duration)",
  1537. ],
  1538. "query": "!has:transaction",
  1539. "statsPeriod": "24h",
  1540. "dataset": "metricsEnhanced",
  1541. "per_page": 50,
  1542. }
  1543. response = self.do_request(query)
  1544. assert response.status_code == 400, response.content
  1545. def test_apdex_transaction_threshold(self):
  1546. ProjectTransactionThresholdOverride.objects.create(
  1547. transaction="foo_transaction",
  1548. project=self.project,
  1549. organization=self.project.organization,
  1550. threshold=600,
  1551. metric=TransactionMetric.LCP.value,
  1552. )
  1553. ProjectTransactionThresholdOverride.objects.create(
  1554. transaction="bar_transaction",
  1555. project=self.project,
  1556. organization=self.project.organization,
  1557. threshold=600,
  1558. metric=TransactionMetric.LCP.value,
  1559. )
  1560. self.store_transaction_metric(
  1561. 1,
  1562. tags={
  1563. "transaction": "foo_transaction",
  1564. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1565. },
  1566. timestamp=self.min_ago,
  1567. )
  1568. self.store_transaction_metric(
  1569. 1,
  1570. "measurements.lcp",
  1571. tags={
  1572. "transaction": "bar_transaction",
  1573. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1574. },
  1575. timestamp=self.min_ago,
  1576. )
  1577. response = self.do_request(
  1578. {
  1579. "field": [
  1580. "transaction",
  1581. "apdex()",
  1582. ],
  1583. "orderby": ["apdex()"],
  1584. "query": "event.type:transaction",
  1585. "dataset": "metrics",
  1586. "per_page": 50,
  1587. }
  1588. )
  1589. assert len(response.data["data"]) == 2
  1590. data = response.data["data"]
  1591. meta = response.data["meta"]
  1592. field_meta = meta["fields"]
  1593. assert data[0]["transaction"] == "bar_transaction"
  1594. # Threshold is lcp based
  1595. assert data[0]["apdex()"] == 1
  1596. assert data[1]["transaction"] == "foo_transaction"
  1597. # Threshold is lcp based
  1598. assert data[1]["apdex()"] == 0
  1599. assert meta["isMetricsData"]
  1600. assert field_meta["transaction"] == "string"
  1601. assert field_meta["apdex()"] == "number"
  1602. def test_apdex_project_threshold(self):
  1603. ProjectTransactionThreshold.objects.create(
  1604. project=self.project,
  1605. organization=self.project.organization,
  1606. threshold=600,
  1607. metric=TransactionMetric.LCP.value,
  1608. )
  1609. self.store_transaction_metric(
  1610. 1,
  1611. tags={
  1612. "transaction": "foo_transaction",
  1613. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1614. },
  1615. timestamp=self.min_ago,
  1616. )
  1617. self.store_transaction_metric(
  1618. 1,
  1619. "measurements.lcp",
  1620. tags={
  1621. "transaction": "bar_transaction",
  1622. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1623. },
  1624. timestamp=self.min_ago,
  1625. )
  1626. response = self.do_request(
  1627. {
  1628. "field": [
  1629. "transaction",
  1630. "apdex()",
  1631. ],
  1632. "orderby": ["apdex()"],
  1633. "query": "event.type:transaction",
  1634. "dataset": "metrics",
  1635. "per_page": 50,
  1636. }
  1637. )
  1638. assert response.status_code == 200, response.content
  1639. assert len(response.data["data"]) == 2
  1640. data = response.data["data"]
  1641. meta = response.data["meta"]
  1642. field_meta = meta["fields"]
  1643. assert data[0]["transaction"] == "bar_transaction"
  1644. # Threshold is lcp based
  1645. assert data[0]["apdex()"] == 1
  1646. assert data[1]["transaction"] == "foo_transaction"
  1647. # Threshold is lcp based
  1648. assert data[1]["apdex()"] == 0
  1649. assert meta["isMetricsData"]
  1650. assert field_meta["transaction"] == "string"
  1651. assert field_meta["apdex()"] == "number"
  1652. def test_apdex_satisfaction_param(self):
  1653. for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
  1654. query = {
  1655. "project": [self.project.id],
  1656. "field": [
  1657. "transaction",
  1658. function,
  1659. ],
  1660. "statsPeriod": "24h",
  1661. "dataset": "metricsEnhanced",
  1662. "per_page": 50,
  1663. }
  1664. response = self.do_request(query)
  1665. assert response.status_code == 200, response.content
  1666. assert len(response.data["data"]) == 0
  1667. meta = response.data["meta"]
  1668. assert not meta["isMetricsData"], function
  1669. query = {
  1670. "project": [self.project.id],
  1671. "field": [
  1672. "transaction",
  1673. function,
  1674. ],
  1675. "statsPeriod": "24h",
  1676. "dataset": "metrics",
  1677. "per_page": 50,
  1678. }
  1679. response = self.do_request(query)
  1680. assert response.status_code == 400, function
  1681. assert b"threshold parameter" in response.content, function
  1682. def test_mobile_metrics(self):
  1683. self.store_transaction_metric(
  1684. 0.4,
  1685. "measurements.frames_frozen_rate",
  1686. tags={
  1687. "transaction": "bar_transaction",
  1688. },
  1689. timestamp=self.min_ago,
  1690. )
  1691. query = {
  1692. "project": [self.project.id],
  1693. "field": [
  1694. "transaction",
  1695. "p50(measurements.frames_frozen_rate)",
  1696. ],
  1697. "statsPeriod": "24h",
  1698. "dataset": "metrics",
  1699. "per_page": 50,
  1700. }
  1701. response = self.do_request(query)
  1702. assert response.status_code == 200, response.content
  1703. assert len(response.data["data"]) == 1
  1704. assert response.data["data"][0]["p50(measurements.frames_frozen_rate)"] == 0.4
  1705. def test_merge_null_unparam(self):
  1706. self.store_transaction_metric(
  1707. 1,
  1708. # Transaction: unparam
  1709. tags={
  1710. "transaction": "<< unparameterized >>",
  1711. },
  1712. timestamp=self.min_ago,
  1713. )
  1714. self.store_transaction_metric(
  1715. 2,
  1716. # Transaction:null
  1717. tags={},
  1718. timestamp=self.min_ago,
  1719. )
  1720. query = {
  1721. "project": [self.project.id],
  1722. "field": [
  1723. "transaction",
  1724. "p50(transaction.duration)",
  1725. ],
  1726. "statsPeriod": "24h",
  1727. "dataset": "metrics",
  1728. "per_page": 50,
  1729. }
  1730. response = self.do_request(query)
  1731. assert response.status_code == 200, response.content
  1732. assert len(response.data["data"]) == 1
  1733. assert response.data["data"][0]["p50(transaction.duration)"] == 1.5
  1734. def test_unparam_filter(self):
  1735. self.store_transaction_metric(
  1736. 1,
  1737. # Transaction: unparam
  1738. tags={
  1739. "transaction": "<< unparameterized >>",
  1740. },
  1741. timestamp=self.min_ago,
  1742. )
  1743. self.store_transaction_metric(
  1744. 2,
  1745. # Transaction:null
  1746. tags={},
  1747. timestamp=self.min_ago,
  1748. )
  1749. self.store_transaction_metric(
  1750. 3,
  1751. tags={
  1752. "transaction": "foo_transaction",
  1753. },
  1754. timestamp=self.min_ago,
  1755. )
  1756. query = {
  1757. "project": [self.project.id],
  1758. "field": [
  1759. "transaction",
  1760. "count()",
  1761. ],
  1762. "query": 'transaction:"<< unparameterized >>"',
  1763. "statsPeriod": "24h",
  1764. "dataset": "metrics",
  1765. "per_page": 50,
  1766. }
  1767. self.wait_for_metric_count(self.project, 3)
  1768. response = self.do_request(query)
  1769. assert response.status_code == 200, response.content
  1770. assert len(response.data["data"]) == 1
  1771. assert response.data["data"][0]["transaction"] == "<< unparameterized >>"
  1772. assert response.data["data"][0]["count()"] == 2
  1773. def test_custom_measurements_without_function(self):
  1774. self.store_transaction_metric(
  1775. 33,
  1776. metric="measurements.datacenter_memory",
  1777. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1778. entity="metrics_distributions",
  1779. tags={"transaction": "foo_transaction"},
  1780. timestamp=self.min_ago,
  1781. )
  1782. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1783. transaction_data["measurements"]["datacenter_memory"] = {
  1784. "value": 33,
  1785. "unit": "petabyte",
  1786. }
  1787. self.store_event(transaction_data, self.project.id)
  1788. measurement = "measurements.datacenter_memory"
  1789. response = self.do_request(
  1790. {
  1791. "field": ["transaction", measurement],
  1792. "query": "measurements.datacenter_memory:33pb",
  1793. "dataset": "discover",
  1794. }
  1795. )
  1796. assert response.status_code == 200, response.content
  1797. data = response.data["data"]
  1798. assert len(data) == 1
  1799. assert data[0][measurement] == 33
  1800. meta = response.data["meta"]
  1801. field_meta = meta["fields"]
  1802. unit_meta = meta["units"]
  1803. assert field_meta[measurement] == "size"
  1804. assert unit_meta[measurement] == "petabyte"
  1805. assert not meta["isMetricsData"]
  1806. def test_custom_measurements_with_function(self):
  1807. self.store_transaction_metric(
  1808. 33,
  1809. metric="measurements.datacenter_memory",
  1810. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1811. entity="metrics_distributions",
  1812. tags={"transaction": "foo_transaction"},
  1813. timestamp=self.min_ago,
  1814. )
  1815. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1816. transaction_data["measurements"]["datacenter_memory"] = {
  1817. "value": 33,
  1818. "unit": "petabyte",
  1819. }
  1820. self.store_event(transaction_data, self.project.id)
  1821. measurement = "p50(measurements.datacenter_memory)"
  1822. response = self.do_request(
  1823. {
  1824. "field": ["transaction", measurement],
  1825. "query": "measurements.datacenter_memory:33pb",
  1826. "dataset": "discover",
  1827. }
  1828. )
  1829. assert response.status_code == 200, response.content
  1830. data = response.data["data"]
  1831. assert len(data) == 1
  1832. assert data[0][measurement] == 33
  1833. meta = response.data["meta"]
  1834. field_meta = meta["fields"]
  1835. unit_meta = meta["units"]
  1836. assert field_meta[measurement] == "size"
  1837. assert unit_meta[measurement] == "petabyte"
  1838. assert not meta["isMetricsData"]
  1839. def test_custom_measurements_equation(self):
  1840. self.store_transaction_metric(
  1841. 33,
  1842. metric="measurements.datacenter_memory",
  1843. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1844. entity="metrics_distributions",
  1845. tags={"transaction": "foo_transaction"},
  1846. timestamp=self.min_ago,
  1847. )
  1848. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1849. transaction_data["measurements"]["datacenter_memory"] = {
  1850. "value": 33,
  1851. "unit": "petabyte",
  1852. }
  1853. self.store_event(transaction_data, self.project.id)
  1854. response = self.do_request(
  1855. {
  1856. "field": [
  1857. "transaction",
  1858. "measurements.datacenter_memory",
  1859. "equation|measurements.datacenter_memory / 3",
  1860. ],
  1861. "query": "",
  1862. "dataset": "discover",
  1863. }
  1864. )
  1865. assert response.status_code == 200, response.content
  1866. data = response.data["data"]
  1867. assert len(data) == 1
  1868. assert data[0]["measurements.datacenter_memory"] == 33
  1869. assert data[0]["equation|measurements.datacenter_memory / 3"] == 11
  1870. meta = response.data["meta"]
  1871. assert not meta["isMetricsData"]
  1872. def test_transaction_wildcard(self):
  1873. self.store_transaction_metric(
  1874. 1,
  1875. tags={"transaction": "foo_transaction"},
  1876. timestamp=self.min_ago,
  1877. )
  1878. self.store_transaction_metric(
  1879. 1,
  1880. tags={"transaction": "bar_transaction"},
  1881. timestamp=self.min_ago,
  1882. )
  1883. response = self.do_request(
  1884. {
  1885. "field": [
  1886. "transaction",
  1887. "p90()",
  1888. ],
  1889. "query": "transaction:foo*",
  1890. "dataset": "metrics",
  1891. }
  1892. )
  1893. assert response.status_code == 200, response.content
  1894. data = response.data["data"]
  1895. assert len(data) == 1
  1896. assert data[0]["p90()"] == 1
  1897. meta = response.data["meta"]
  1898. assert meta["isMetricsData"]
  1899. assert data[0]["transaction"] == "foo_transaction"
  1900. def test_transaction_status_wildcard(self):
  1901. self.store_transaction_metric(
  1902. 1,
  1903. tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
  1904. timestamp=self.min_ago,
  1905. )
  1906. response = self.do_request(
  1907. {
  1908. "field": [
  1909. "transaction",
  1910. "p90()",
  1911. ],
  1912. "query": "transaction.status:f*bar",
  1913. "dataset": "metrics",
  1914. }
  1915. )
  1916. assert response.status_code == 200, response.content
  1917. data = response.data["data"]
  1918. assert len(data) == 1
  1919. assert data[0]["p90()"] == 1
  1920. meta = response.data["meta"]
  1921. assert meta["isMetricsData"]
  1922. def test_http_error_rate(self):
  1923. self.store_transaction_metric(
  1924. 1,
  1925. tags={
  1926. "transaction": "foo_transaction",
  1927. "transaction.status": "foobar",
  1928. "http.status_code": "500",
  1929. },
  1930. timestamp=self.min_ago,
  1931. )
  1932. self.store_transaction_metric(
  1933. 1,
  1934. tags={"transaction": "bar_transaction", "http.status_code": "400"},
  1935. timestamp=self.min_ago,
  1936. )
  1937. response = self.do_request(
  1938. {
  1939. "field": [
  1940. "http_error_rate()",
  1941. ],
  1942. "dataset": "metrics",
  1943. }
  1944. )
  1945. assert response.status_code == 200, response.content
  1946. data = response.data["data"]
  1947. assert len(data) == 1
  1948. assert data[0]["http_error_rate()"] == 0.5
  1949. meta = response.data["meta"]
  1950. assert meta["isMetricsData"]
  1951. def test_time_spent(self):
  1952. self.store_transaction_metric(
  1953. 1,
  1954. tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
  1955. timestamp=self.min_ago,
  1956. )
  1957. self.store_transaction_metric(
  1958. 1,
  1959. tags={"transaction": "bar_transaction"},
  1960. timestamp=self.min_ago,
  1961. )
  1962. response = self.do_request(
  1963. {
  1964. "field": [
  1965. "transaction",
  1966. "time_spent_percentage()",
  1967. ],
  1968. "dataset": "metrics",
  1969. }
  1970. )
  1971. assert response.status_code == 200, response.content
  1972. data = response.data["data"]
  1973. assert len(data) == 2
  1974. assert data[0]["time_spent_percentage()"] == 0.5
  1975. meta = response.data["meta"]
  1976. assert meta["isMetricsData"]
  1977. def test_has_filter(self):
  1978. self.store_transaction_metric(
  1979. 1,
  1980. tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
  1981. timestamp=self.min_ago,
  1982. )
  1983. response = self.do_request(
  1984. {
  1985. "field": [
  1986. "transaction",
  1987. "p50()",
  1988. ],
  1989. # For the metrics dataset, has on metrics should be no-ops
  1990. "query": "has:measurements.frames_frozen_rate",
  1991. "dataset": "metrics",
  1992. }
  1993. )
  1994. assert response.status_code == 200, response.content
  1995. data = response.data["data"]
  1996. assert len(data) == 1
  1997. assert data[0]["p50()"] == 1
  1998. meta = response.data["meta"]
  1999. assert meta["isMetricsData"]
  2000. response = self.do_request(
  2001. {
  2002. "field": [
  2003. "transaction",
  2004. "p50()",
  2005. ],
  2006. "query": "has:transaction.status",
  2007. "dataset": "metrics",
  2008. }
  2009. )
  2010. assert response.status_code == 200, response.content
  2011. data = response.data["data"]
  2012. assert len(data) == 1
  2013. assert data[0]["p50()"] == 1
  2014. meta = response.data["meta"]
  2015. assert meta["isMetricsData"]
  2016. def test_not_has_filter(self):
  2017. self.store_transaction_metric(
  2018. 1,
  2019. tags={"transaction": "foo_transaction", "transaction.status": "foobar"},
  2020. timestamp=self.min_ago,
  2021. )
  2022. response = self.do_request(
  2023. {
  2024. "field": [
  2025. "transaction",
  2026. "p50()",
  2027. ],
  2028. "query": "!has:transaction.status",
  2029. "dataset": "metrics",
  2030. }
  2031. )
  2032. assert response.status_code == 200, response.content
  2033. data = response.data["data"]
  2034. assert len(data) == 0
  2035. meta = response.data["meta"]
  2036. assert meta["isMetricsData"]
  2037. response = self.do_request(
  2038. {
  2039. "field": [
  2040. "transaction",
  2041. "p50()",
  2042. ],
  2043. # Doing !has on the metrics dataset doesn't really make sense
  2044. "query": "!has:measurements.frames_frozen_rate",
  2045. "dataset": "metrics",
  2046. }
  2047. )
  2048. assert response.status_code == 400, response.content
  2049. def test_p50_with_count(self):
  2050. """Implicitly test the fact that percentiles are their own 'dataset'"""
  2051. self.store_transaction_metric(
  2052. 1,
  2053. tags={"transaction": "foo_transaction"},
  2054. timestamp=self.min_ago,
  2055. )
  2056. response = self.do_request(
  2057. {
  2058. "field": ["title", "p50()", "count()"],
  2059. "query": "event.type:transaction",
  2060. "dataset": "metrics",
  2061. "project": self.project.id,
  2062. "per_page": 50,
  2063. }
  2064. )
  2065. assert response.status_code == 200, response.content
  2066. assert len(response.data["data"]) == 1
  2067. data = response.data["data"]
  2068. meta = response.data["meta"]
  2069. field_meta = meta["fields"]
  2070. assert data[0]["title"] == "foo_transaction"
  2071. assert data[0]["p50()"] == 1
  2072. assert data[0]["count()"] == 1
  2073. assert meta["isMetricsData"]
  2074. assert field_meta["title"] == "string"
  2075. assert field_meta["p50()"] == "duration"
  2076. assert field_meta["count()"] == "integer"
  2077. def test_p75_with_count_and_more_groupby(self):
  2078. """Implicitly test the fact that percentiles are their own 'dataset'"""
  2079. self.store_transaction_metric(
  2080. 1,
  2081. tags={"transaction": "foo_transaction"},
  2082. timestamp=self.min_ago,
  2083. )
  2084. self.store_transaction_metric(
  2085. 5,
  2086. tags={"transaction": "bar_transaction"},
  2087. timestamp=self.min_ago,
  2088. )
  2089. self.store_transaction_metric(
  2090. 5,
  2091. tags={"transaction": "bar_transaction"},
  2092. timestamp=self.min_ago,
  2093. )
  2094. response = self.do_request(
  2095. {
  2096. "field": [
  2097. "title",
  2098. "project",
  2099. "p75()",
  2100. "count()",
  2101. ],
  2102. "query": "event.type:transaction",
  2103. "orderby": "count()",
  2104. "dataset": "metrics",
  2105. "project": self.project.id,
  2106. "per_page": 50,
  2107. }
  2108. )
  2109. assert response.status_code == 200, response.content
  2110. assert len(response.data["data"]) == 2
  2111. data = response.data["data"]
  2112. meta = response.data["meta"]
  2113. field_meta = meta["fields"]
  2114. assert data[0]["title"] == "foo_transaction"
  2115. assert data[0]["p75()"] == 1
  2116. assert data[0]["count()"] == 1
  2117. assert data[1]["title"] == "bar_transaction"
  2118. assert data[1]["p75()"] == 5
  2119. assert data[1]["count()"] == 2
  2120. assert meta["isMetricsData"]
  2121. assert field_meta["title"] == "string"
  2122. assert field_meta["p75()"] == "duration"
  2123. assert field_meta["count()"] == "integer"
  2124. def test_title_and_transaction_alias(self):
  2125. # Title and transaction are aliases to the same column
  2126. self.store_transaction_metric(
  2127. 1,
  2128. tags={"transaction": "foo_transaction"},
  2129. timestamp=self.min_ago,
  2130. )
  2131. response = self.do_request(
  2132. {
  2133. "field": [
  2134. "title",
  2135. "transaction",
  2136. "p75()",
  2137. ],
  2138. "query": "event.type:transaction",
  2139. "orderby": "p75()",
  2140. "dataset": "metrics",
  2141. "project": self.project.id,
  2142. "per_page": 50,
  2143. }
  2144. )
  2145. assert response.status_code == 200, response.content
  2146. assert len(response.data["data"]) == 1
  2147. data = response.data["data"]
  2148. meta = response.data["meta"]
  2149. field_meta = meta["fields"]
  2150. assert data[0]["title"] == "foo_transaction"
  2151. assert data[0]["transaction"] == "foo_transaction"
  2152. assert data[0]["p75()"] == 1
  2153. assert meta["isMetricsData"]
  2154. assert field_meta["title"] == "string"
  2155. assert field_meta["transaction"] == "string"
  2156. assert field_meta["p75()"] == "duration"
  2157. def test_maintain_sort_order_across_datasets(self):
  2158. self.store_transaction_metric(
  2159. 1,
  2160. tags={"transaction": "foo_transaction"},
  2161. timestamp=self.min_ago,
  2162. )
  2163. self.store_transaction_metric(
  2164. 1,
  2165. metric="user",
  2166. tags={"transaction": "bar_transaction"},
  2167. timestamp=self.min_ago,
  2168. )
  2169. self.store_transaction_metric(
  2170. 5,
  2171. tags={"transaction": "bar_transaction"},
  2172. timestamp=self.min_ago,
  2173. )
  2174. self.store_transaction_metric(
  2175. 5,
  2176. tags={"transaction": "bar_transaction"},
  2177. timestamp=self.min_ago,
  2178. )
  2179. response = self.do_request(
  2180. {
  2181. "field": [
  2182. "title",
  2183. "project",
  2184. "count()",
  2185. "count_unique(user)",
  2186. ],
  2187. "query": "event.type:transaction",
  2188. "orderby": "count()",
  2189. "dataset": "metrics",
  2190. "project": self.project.id,
  2191. "per_page": 50,
  2192. }
  2193. )
  2194. assert response.status_code == 200, response.content
  2195. data = response.data["data"]
  2196. meta = response.data["meta"]
  2197. field_meta = meta["fields"]
  2198. assert len(data) == 2
  2199. assert data[0]["title"] == "foo_transaction"
  2200. assert data[0]["count()"] == 1
  2201. assert data[0]["count_unique(user)"] == 0
  2202. assert data[1]["title"] == "bar_transaction"
  2203. assert data[1]["count()"] == 2
  2204. assert data[1]["count_unique(user)"] == 1
  2205. assert meta["isMetricsData"]
  2206. assert field_meta["title"] == "string"
  2207. assert field_meta["count()"] == "integer"
  2208. assert field_meta["count_unique(user)"] == "integer"
  2209. def test_avg_compare(self):
  2210. self.store_transaction_metric(
  2211. 100,
  2212. timestamp=self.min_ago,
  2213. tags={"release": "foo"},
  2214. )
  2215. self.store_transaction_metric(
  2216. 10,
  2217. timestamp=self.min_ago,
  2218. tags={"release": "bar"},
  2219. )
  2220. for function_name in [
  2221. "avg_compare(transaction.duration, release, foo, bar)",
  2222. 'avg_compare(transaction.duration, release, "foo", "bar")',
  2223. ]:
  2224. response = self.do_request(
  2225. {
  2226. "field": [function_name],
  2227. "query": "",
  2228. "project": self.project.id,
  2229. "dataset": "metrics",
  2230. }
  2231. )
  2232. assert response.status_code == 200, response.content
  2233. data = response.data["data"]
  2234. meta = response.data["meta"]
  2235. assert len(data) == 1
  2236. assert data[0][function_name] == -0.9
  2237. assert meta["dataset"] == "metrics"
  2238. assert meta["fields"][function_name] == "percent_change"
  2239. def test_avg_if(self):
  2240. self.store_transaction_metric(
  2241. 100,
  2242. timestamp=self.min_ago,
  2243. tags={"release": "foo"},
  2244. )
  2245. self.store_transaction_metric(
  2246. 200,
  2247. timestamp=self.min_ago,
  2248. tags={"release": "foo"},
  2249. )
  2250. self.store_transaction_metric(
  2251. 10,
  2252. timestamp=self.min_ago,
  2253. tags={"release": "bar"},
  2254. )
  2255. response = self.do_request(
  2256. {
  2257. "field": ["avg_if(transaction.duration, release, foo)"],
  2258. "query": "",
  2259. "project": self.project.id,
  2260. "dataset": "metrics",
  2261. }
  2262. )
  2263. assert response.status_code == 200, response.content
  2264. data = response.data["data"]
  2265. meta = response.data["meta"]
  2266. assert len(data) == 1
  2267. assert data[0]["avg_if(transaction.duration, release, foo)"] == 150
  2268. assert meta["dataset"] == "metrics"
  2269. assert meta["fields"]["avg_if(transaction.duration, release, foo)"] == "duration"
  2270. def test_device_class(self):
  2271. self.store_transaction_metric(
  2272. 100,
  2273. timestamp=self.min_ago,
  2274. tags={"device.class": "1"},
  2275. )
  2276. self.store_transaction_metric(
  2277. 200,
  2278. timestamp=self.min_ago,
  2279. tags={"device.class": "2"},
  2280. )
  2281. self.store_transaction_metric(
  2282. 300,
  2283. timestamp=self.min_ago,
  2284. tags={"device.class": ""},
  2285. )
  2286. response = self.do_request(
  2287. {
  2288. "field": ["device.class", "p95()"],
  2289. "query": "",
  2290. "orderby": "p95()",
  2291. "project": self.project.id,
  2292. "dataset": "metrics",
  2293. }
  2294. )
  2295. assert response.status_code == 200, response.content
  2296. data = response.data["data"]
  2297. meta = response.data["meta"]
  2298. assert len(data) == 3
  2299. # Need to actually check the dict since the level for 1 isn't guaranteed to stay `low` or `medium`
  2300. assert data[0]["device.class"] == map_device_class_level("1")
  2301. assert data[1]["device.class"] == map_device_class_level("2")
  2302. assert data[2]["device.class"] == "Unknown"
  2303. assert meta["fields"]["device.class"] == "string"
  2304. def test_device_class_filter(self):
  2305. self.store_transaction_metric(
  2306. 300,
  2307. timestamp=self.min_ago,
  2308. tags={"device.class": "1"},
  2309. )
  2310. # Need to actually check the dict since the level for 1 isn't guaranteed to stay `low`
  2311. level = map_device_class_level("1")
  2312. response = self.do_request(
  2313. {
  2314. "field": ["device.class", "count()"],
  2315. "query": f"device.class:{level}",
  2316. "orderby": "count()",
  2317. "project": self.project.id,
  2318. "dataset": "metrics",
  2319. }
  2320. )
  2321. assert response.status_code == 200, response.content
  2322. data = response.data["data"]
  2323. meta = response.data["meta"]
  2324. assert len(data) == 1
  2325. assert data[0]["device.class"] == level
  2326. assert meta["fields"]["device.class"] == "string"
  2327. def test_performance_score(self):
  2328. self.store_transaction_metric(
  2329. 0.03,
  2330. metric="measurements.score.lcp",
  2331. tags={"transaction": "foo_transaction"},
  2332. timestamp=self.min_ago,
  2333. )
  2334. self.store_transaction_metric(
  2335. 0.30,
  2336. metric="measurements.score.weight.lcp",
  2337. tags={"transaction": "foo_transaction"},
  2338. timestamp=self.min_ago,
  2339. )
  2340. self.store_transaction_metric(
  2341. 0.35,
  2342. metric="measurements.score.fcp",
  2343. tags={"transaction": "foo_transaction"},
  2344. timestamp=self.min_ago,
  2345. )
  2346. self.store_transaction_metric(
  2347. 0.70,
  2348. metric="measurements.score.weight.fcp",
  2349. tags={"transaction": "foo_transaction"},
  2350. timestamp=self.min_ago,
  2351. )
  2352. self.store_transaction_metric(
  2353. 0.38,
  2354. metric="measurements.score.total",
  2355. tags={"transaction": "foo_transaction"},
  2356. timestamp=self.min_ago,
  2357. )
  2358. self.store_transaction_metric(
  2359. 1.00,
  2360. metric="measurements.score.lcp",
  2361. tags={"transaction": "foo_transaction"},
  2362. timestamp=self.min_ago,
  2363. )
  2364. self.store_transaction_metric(
  2365. 1.00,
  2366. metric="measurements.score.weight.lcp",
  2367. tags={"transaction": "foo_transaction"},
  2368. timestamp=self.min_ago,
  2369. )
  2370. self.store_transaction_metric(
  2371. 0.00,
  2372. metric="measurements.score.fid",
  2373. tags={"transaction": "foo_transaction"},
  2374. timestamp=self.min_ago,
  2375. )
  2376. # These fid and ttfb scenarios shouldn't really be happening, but we can test them anyways
  2377. self.store_transaction_metric(
  2378. 0.00,
  2379. metric="measurements.score.weight.fid",
  2380. tags={"transaction": "foo_transaction"},
  2381. timestamp=self.min_ago,
  2382. )
  2383. self.store_transaction_metric(
  2384. 1.00,
  2385. metric="measurements.score.ttfb",
  2386. tags={"transaction": "foo_transaction"},
  2387. timestamp=self.min_ago,
  2388. )
  2389. self.store_transaction_metric(
  2390. 0.00,
  2391. metric="measurements.score.weight.ttfb",
  2392. tags={"transaction": "foo_transaction"},
  2393. timestamp=self.min_ago,
  2394. )
  2395. self.store_transaction_metric(
  2396. 1.00,
  2397. metric="measurements.score.total",
  2398. tags={"transaction": "foo_transaction"},
  2399. timestamp=self.min_ago,
  2400. )
  2401. # INP metrics
  2402. self.store_transaction_metric(
  2403. 0.80,
  2404. metric="measurements.score.inp",
  2405. tags={"transaction": "foo_transaction"},
  2406. timestamp=self.min_ago,
  2407. )
  2408. self.store_transaction_metric(
  2409. 1.00,
  2410. metric="measurements.score.weight.inp",
  2411. tags={"transaction": "foo_transaction"},
  2412. timestamp=self.min_ago,
  2413. )
  2414. self.store_transaction_metric(
  2415. 0.80,
  2416. metric="measurements.score.total",
  2417. tags={"transaction": "foo_transaction"},
  2418. timestamp=self.min_ago,
  2419. )
  2420. response = self.do_request(
  2421. {
  2422. "field": [
  2423. "transaction",
  2424. "performance_score(measurements.score.lcp)",
  2425. "performance_score(measurements.score.fcp)",
  2426. "performance_score(measurements.score.fid)",
  2427. "performance_score(measurements.score.ttfb)",
  2428. "performance_score(measurements.score.inp)",
  2429. ],
  2430. "query": "event.type:transaction",
  2431. "dataset": "metrics",
  2432. "per_page": 50,
  2433. }
  2434. )
  2435. assert response.status_code == 200, response.content
  2436. assert len(response.data["data"]) == 1
  2437. data = response.data["data"]
  2438. meta = response.data["meta"]
  2439. field_meta = meta["fields"]
  2440. assert data[0]["performance_score(measurements.score.lcp)"] == 0.7923076923076923
  2441. assert data[0]["performance_score(measurements.score.fcp)"] == 0.5
  2442. assert data[0]["performance_score(measurements.score.fid)"] == 0
  2443. assert data[0]["performance_score(measurements.score.ttfb)"] == 0
  2444. assert data[0]["performance_score(measurements.score.inp)"] == 0.8
  2445. assert meta["isMetricsData"]
  2446. assert field_meta["performance_score(measurements.score.lcp)"] == "number"
  2447. def test_performance_score_boundaries(self):
  2448. # Scores shouldn't exceed 1 or go below 0, but we can test these boundaries anyways
  2449. self.store_transaction_metric(
  2450. 0.65,
  2451. metric="measurements.score.lcp",
  2452. tags={"transaction": "foo_transaction"},
  2453. timestamp=self.min_ago,
  2454. )
  2455. self.store_transaction_metric(
  2456. 0.30,
  2457. metric="measurements.score.weight.lcp",
  2458. tags={"transaction": "foo_transaction"},
  2459. timestamp=self.min_ago,
  2460. )
  2461. self.store_transaction_metric(
  2462. -0.35,
  2463. metric="measurements.score.fcp",
  2464. tags={"transaction": "foo_transaction"},
  2465. timestamp=self.min_ago,
  2466. )
  2467. self.store_transaction_metric(
  2468. 0.70,
  2469. metric="measurements.score.weight.fcp",
  2470. tags={"transaction": "foo_transaction"},
  2471. timestamp=self.min_ago,
  2472. )
  2473. self.store_transaction_metric(
  2474. 0.3,
  2475. metric="measurements.score.total",
  2476. tags={"transaction": "foo_transaction"},
  2477. timestamp=self.min_ago,
  2478. )
  2479. response = self.do_request(
  2480. {
  2481. "field": [
  2482. "transaction",
  2483. "performance_score(measurements.score.lcp)",
  2484. "performance_score(measurements.score.fcp)",
  2485. ],
  2486. "query": "event.type:transaction",
  2487. "dataset": "metrics",
  2488. "per_page": 50,
  2489. }
  2490. )
  2491. assert response.status_code == 200, response.content
  2492. assert len(response.data["data"]) == 1
  2493. data = response.data["data"]
  2494. meta = response.data["meta"]
  2495. field_meta = meta["fields"]
  2496. assert data[0]["performance_score(measurements.score.lcp)"] == 1.0
  2497. assert data[0]["performance_score(measurements.score.fcp)"] == 0.0
  2498. assert meta["isMetricsData"]
  2499. assert field_meta["performance_score(measurements.score.lcp)"] == "number"
  2500. def test_weighted_performance_score(self):
  2501. self.store_transaction_metric(
  2502. 0.03,
  2503. metric="measurements.score.lcp",
  2504. tags={"transaction": "foo_transaction"},
  2505. timestamp=self.min_ago,
  2506. )
  2507. self.store_transaction_metric(
  2508. 0.30,
  2509. metric="measurements.score.weight.lcp",
  2510. tags={"transaction": "foo_transaction"},
  2511. timestamp=self.min_ago,
  2512. )
  2513. self.store_transaction_metric(
  2514. 0.03,
  2515. metric="measurements.score.total",
  2516. tags={"transaction": "foo_transaction"},
  2517. timestamp=self.min_ago,
  2518. )
  2519. self.store_transaction_metric(
  2520. 1.00,
  2521. metric="measurements.score.lcp",
  2522. tags={"transaction": "foo_transaction"},
  2523. timestamp=self.min_ago,
  2524. )
  2525. self.store_transaction_metric(
  2526. 1.00,
  2527. metric="measurements.score.weight.lcp",
  2528. tags={"transaction": "foo_transaction"},
  2529. timestamp=self.min_ago,
  2530. )
  2531. self.store_transaction_metric(
  2532. 1.00,
  2533. metric="measurements.score.total",
  2534. tags={"transaction": "foo_transaction"},
  2535. timestamp=self.min_ago,
  2536. )
  2537. self.store_transaction_metric(
  2538. 0.00,
  2539. metric="measurements.score.total",
  2540. tags={"transaction": "foo_transaction"},
  2541. timestamp=self.min_ago,
  2542. )
  2543. self.store_transaction_metric(
  2544. 0.80,
  2545. metric="measurements.score.inp",
  2546. tags={"transaction": "foo_transaction"},
  2547. timestamp=self.min_ago,
  2548. )
  2549. self.store_transaction_metric(
  2550. 1.00,
  2551. metric="measurements.score.weight.lcp",
  2552. tags={"transaction": "foo_transaction"},
  2553. timestamp=self.min_ago,
  2554. )
  2555. self.store_transaction_metric(
  2556. 0.80,
  2557. metric="measurements.score.total",
  2558. tags={"transaction": "foo_transaction"},
  2559. timestamp=self.min_ago,
  2560. )
  2561. response = self.do_request(
  2562. {
  2563. "field": [
  2564. "transaction",
  2565. "weighted_performance_score(measurements.score.lcp)",
  2566. "weighted_performance_score(measurements.score.inp)",
  2567. ],
  2568. "query": "event.type:transaction",
  2569. "dataset": "metrics",
  2570. "per_page": 50,
  2571. }
  2572. )
  2573. assert response.status_code == 200, response.content
  2574. assert len(response.data["data"]) == 1
  2575. data = response.data["data"]
  2576. meta = response.data["meta"]
  2577. field_meta = meta["fields"]
  2578. assert data[0]["weighted_performance_score(measurements.score.lcp)"] == 0.2575
  2579. assert data[0]["weighted_performance_score(measurements.score.inp)"] == 0.2
  2580. assert meta["isMetricsData"]
  2581. assert field_meta["weighted_performance_score(measurements.score.lcp)"] == "number"
  2582. def test_invalid_performance_score_column(self):
  2583. self.store_transaction_metric(
  2584. 0.03,
  2585. metric="measurements.score.total",
  2586. tags={"transaction": "foo_transaction"},
  2587. timestamp=self.min_ago,
  2588. )
  2589. response = self.do_request(
  2590. {
  2591. "field": [
  2592. "transaction",
  2593. "performance_score(measurements.score.fp)",
  2594. ],
  2595. "query": "event.type:transaction",
  2596. "dataset": "metrics",
  2597. "per_page": 50,
  2598. }
  2599. )
  2600. assert response.status_code == 400, response.content
  2601. def test_invalid_weighted_performance_score_column(self):
  2602. self.store_transaction_metric(
  2603. 0.03,
  2604. metric="measurements.score.total",
  2605. tags={"transaction": "foo_transaction"},
  2606. timestamp=self.min_ago,
  2607. )
  2608. response = self.do_request(
  2609. {
  2610. "field": [
  2611. "transaction",
  2612. "weighted_performance_score(measurements.score.fp)",
  2613. ],
  2614. "query": "event.type:transaction",
  2615. "dataset": "metrics",
  2616. "per_page": 50,
  2617. }
  2618. )
  2619. assert response.status_code == 400, response.content
  2620. def test_no_weighted_performance_score_column(self):
  2621. self.store_transaction_metric(
  2622. 0.0,
  2623. metric="measurements.score.lcp",
  2624. tags={"transaction": "foo_transaction"},
  2625. timestamp=self.min_ago,
  2626. )
  2627. response = self.do_request(
  2628. {
  2629. "field": [
  2630. "transaction",
  2631. "weighted_performance_score(measurements.score.lcp)",
  2632. ],
  2633. "query": "event.type:transaction",
  2634. "dataset": "metrics",
  2635. "per_page": 50,
  2636. }
  2637. )
  2638. assert response.status_code == 200, response.content
  2639. assert len(response.data["data"]) == 1
  2640. data = response.data["data"]
  2641. meta = response.data["meta"]
  2642. field_meta = meta["fields"]
  2643. assert data[0]["weighted_performance_score(measurements.score.lcp)"] == 0.0
  2644. assert meta["isMetricsData"]
  2645. assert field_meta["weighted_performance_score(measurements.score.lcp)"] == "number"
  2646. def test_opportunity_score(self):
  2647. self.store_transaction_metric(
  2648. 0.03,
  2649. metric="measurements.score.lcp",
  2650. tags={"transaction": "foo_transaction"},
  2651. timestamp=self.min_ago,
  2652. )
  2653. self.store_transaction_metric(
  2654. 0.30,
  2655. metric="measurements.score.weight.lcp",
  2656. tags={"transaction": "foo_transaction"},
  2657. timestamp=self.min_ago,
  2658. )
  2659. self.store_transaction_metric(
  2660. 0.40,
  2661. metric="measurements.score.fcp",
  2662. tags={"transaction": "foo_transaction"},
  2663. timestamp=self.min_ago,
  2664. )
  2665. self.store_transaction_metric(
  2666. 0.70,
  2667. metric="measurements.score.weight.fcp",
  2668. tags={"transaction": "foo_transaction"},
  2669. timestamp=self.min_ago,
  2670. )
  2671. self.store_transaction_metric(
  2672. 0.43,
  2673. metric="measurements.score.total",
  2674. tags={"transaction": "foo_transaction"},
  2675. timestamp=self.min_ago,
  2676. )
  2677. self.store_transaction_metric(
  2678. 1.0,
  2679. metric="measurements.score.lcp",
  2680. tags={"transaction": "foo_transaction"},
  2681. timestamp=self.min_ago,
  2682. )
  2683. self.store_transaction_metric(
  2684. 1.0,
  2685. metric="measurements.score.weight.lcp",
  2686. tags={"transaction": "foo_transaction"},
  2687. timestamp=self.min_ago,
  2688. )
  2689. self.store_transaction_metric(
  2690. 1.0,
  2691. metric="measurements.score.total",
  2692. tags={"transaction": "foo_transaction"},
  2693. timestamp=self.min_ago,
  2694. )
  2695. self.store_transaction_metric(
  2696. 0.0,
  2697. metric="measurements.score.total",
  2698. tags={"transaction": "foo_transaction"},
  2699. timestamp=self.min_ago,
  2700. )
  2701. self.store_transaction_metric(
  2702. 0.80,
  2703. metric="measurements.score.inp",
  2704. tags={"transaction": "foo_transaction"},
  2705. timestamp=self.min_ago,
  2706. )
  2707. self.store_transaction_metric(
  2708. 1.00,
  2709. metric="measurements.score.weight.inp",
  2710. tags={"transaction": "foo_transaction"},
  2711. timestamp=self.min_ago,
  2712. )
  2713. self.store_transaction_metric(
  2714. 0.80,
  2715. metric="measurements.score.total",
  2716. tags={"transaction": "foo_transaction"},
  2717. timestamp=self.min_ago,
  2718. )
  2719. response = self.do_request(
  2720. {
  2721. "field": [
  2722. "transaction",
  2723. "opportunity_score(measurements.score.lcp)",
  2724. "opportunity_score(measurements.score.inp)",
  2725. "opportunity_score(measurements.score.total)",
  2726. ],
  2727. "query": "event.type:transaction",
  2728. "dataset": "metrics",
  2729. "per_page": 50,
  2730. }
  2731. )
  2732. assert response.status_code == 200, response.content
  2733. assert len(response.data["data"]) == 1
  2734. data = response.data["data"]
  2735. meta = response.data["meta"]
  2736. assert data[0]["opportunity_score(measurements.score.lcp)"] == 0.27
  2737. # Should be 0.2. Precision issue?
  2738. assert data[0]["opportunity_score(measurements.score.inp)"] == 0.19999999999999996
  2739. assert data[0]["opportunity_score(measurements.score.total)"] == 1.77
  2740. assert meta["isMetricsData"]
  2741. def test_count_scores(self):
  2742. self.store_transaction_metric(
  2743. 0.1,
  2744. metric="measurements.score.total",
  2745. tags={"transaction": "foo_transaction"},
  2746. timestamp=self.min_ago,
  2747. )
  2748. self.store_transaction_metric(
  2749. 0.2,
  2750. metric="measurements.score.total",
  2751. tags={"transaction": "foo_transaction"},
  2752. timestamp=self.min_ago,
  2753. )
  2754. self.store_transaction_metric(
  2755. 0.3,
  2756. metric="measurements.score.total",
  2757. tags={"transaction": "foo_transaction"},
  2758. timestamp=self.min_ago,
  2759. )
  2760. self.store_transaction_metric(
  2761. 0.4,
  2762. metric="measurements.score.total",
  2763. tags={"transaction": "foo_transaction"},
  2764. timestamp=self.min_ago,
  2765. )
  2766. self.store_transaction_metric(
  2767. 0.5,
  2768. metric="measurements.score.lcp",
  2769. tags={"transaction": "foo_transaction"},
  2770. timestamp=self.min_ago,
  2771. )
  2772. self.store_transaction_metric(
  2773. 0.8,
  2774. metric="measurements.score.inp",
  2775. tags={"transaction": "foo_transaction"},
  2776. timestamp=self.min_ago,
  2777. )
  2778. response = self.do_request(
  2779. {
  2780. "field": [
  2781. "transaction",
  2782. "count_scores(measurements.score.total)",
  2783. "count_scores(measurements.score.lcp)",
  2784. "count_scores(measurements.score.inp)",
  2785. ],
  2786. "query": "event.type:transaction",
  2787. "dataset": "metrics",
  2788. "per_page": 50,
  2789. }
  2790. )
  2791. assert response.status_code == 200, response.content
  2792. assert len(response.data["data"]) == 1
  2793. data = response.data["data"]
  2794. meta = response.data["meta"]
  2795. assert data[0]["count_scores(measurements.score.total)"] == 4
  2796. assert data[0]["count_scores(measurements.score.lcp)"] == 1
  2797. assert data[0]["count_scores(measurements.score.inp)"] == 1
  2798. assert meta["isMetricsData"]
  2799. def test_count_starts(self):
  2800. self.store_transaction_metric(
  2801. 200,
  2802. metric="measurements.app_start_warm",
  2803. tags={"transaction": "foo_transaction"},
  2804. timestamp=self.min_ago,
  2805. )
  2806. self.store_transaction_metric(
  2807. 100,
  2808. metric="measurements.app_start_warm",
  2809. tags={"transaction": "foo_transaction"},
  2810. timestamp=self.min_ago,
  2811. )
  2812. self.store_transaction_metric(
  2813. 10,
  2814. metric="measurements.app_start_cold",
  2815. tags={"transaction": "foo_transaction"},
  2816. timestamp=self.min_ago,
  2817. )
  2818. response = self.do_request(
  2819. {
  2820. "field": [
  2821. "transaction",
  2822. "count_starts(measurements.app_start_warm)",
  2823. "count_starts(measurements.app_start_cold)",
  2824. ],
  2825. "query": "event.type:transaction",
  2826. "dataset": "metrics",
  2827. "per_page": 50,
  2828. }
  2829. )
  2830. assert response.status_code == 200, response.content
  2831. assert len(response.data["data"]) == 1
  2832. data = response.data["data"]
  2833. meta = response.data["meta"]
  2834. assert data[0]["count_starts(measurements.app_start_warm)"] == 2
  2835. assert data[0]["count_starts(measurements.app_start_cold)"] == 1
  2836. assert meta["isMetricsData"]
  2837. def test_count_starts_returns_all_counts_when_no_arg_is_passed(self):
  2838. self.store_transaction_metric(
  2839. 200,
  2840. metric="measurements.app_start_warm",
  2841. tags={"transaction": "foo_transaction"},
  2842. timestamp=self.min_ago,
  2843. )
  2844. self.store_transaction_metric(
  2845. 100,
  2846. metric="measurements.app_start_warm",
  2847. tags={"transaction": "foo_transaction"},
  2848. timestamp=self.min_ago,
  2849. )
  2850. self.store_transaction_metric(
  2851. 10,
  2852. metric="measurements.app_start_cold",
  2853. tags={"transaction": "foo_transaction"},
  2854. timestamp=self.min_ago,
  2855. )
  2856. response = self.do_request(
  2857. {
  2858. "field": [
  2859. "transaction",
  2860. "count_total_starts()",
  2861. ],
  2862. "query": "event.type:transaction",
  2863. "dataset": "metrics",
  2864. "per_page": 50,
  2865. }
  2866. )
  2867. assert response.status_code == 200, response.content
  2868. assert len(response.data["data"]) == 1
  2869. data = response.data["data"]
  2870. meta = response.data["meta"]
  2871. assert data[0]["count_total_starts()"] == 3
  2872. assert meta["isMetricsData"]
  2873. def test_timestamp_groupby(self):
  2874. self.store_transaction_metric(
  2875. 0.03,
  2876. tags={"transaction": "foo_transaction", "user": "foo"},
  2877. timestamp=self.min_ago,
  2878. )
  2879. response = self.do_request(
  2880. {
  2881. "field": [
  2882. "transaction",
  2883. "timestamp",
  2884. "count()",
  2885. "count_unique(user)",
  2886. ],
  2887. "query": "event.type:transaction",
  2888. "dataset": "metricsEnhanced",
  2889. "per_page": 50,
  2890. }
  2891. )
  2892. assert response.status_code == 200, response.content
  2893. assert len(response.data["data"]) == 1
  2894. data = response.data["data"]
  2895. meta = response.data["meta"]
  2896. assert data[0]["transaction"] == "foo_transaction"
  2897. assert meta["dataset"] == "metricsEnhanced"
  2898. def test_on_demand_with_mep(self):
  2899. # Store faketag as an OnDemandMetricSpec, which will put faketag into the metrics indexer
  2900. spec = OnDemandMetricSpec(
  2901. field="count()",
  2902. query="user.email:blah@example.com",
  2903. environment="prod",
  2904. groupbys=["faketag"],
  2905. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2906. )
  2907. self.store_on_demand_metric(123, spec=spec)
  2908. # This is the event that we should actually return
  2909. transaction_data = load_data("transaction", timestamp=self.min_ago)
  2910. transaction_data["tags"].append(("faketag", "foo"))
  2911. self.store_event(transaction_data, self.project.id)
  2912. with self.feature({"organizations:mep-use-default-tags": True}):
  2913. response = self.do_request(
  2914. {
  2915. "field": [
  2916. "faketag",
  2917. "count()",
  2918. ],
  2919. "query": "event.type:transaction",
  2920. "dataset": "metricsEnhanced",
  2921. "per_page": 50,
  2922. }
  2923. )
  2924. assert response.status_code == 200, response.content
  2925. assert len(response.data["data"]) == 1
  2926. data = response.data["data"]
  2927. meta = response.data["meta"]
  2928. assert data[0]["faketag"] == "foo"
  2929. assert not meta["isMetricsData"]
  2930. @region_silo_test
  2931. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithOnDemandMetrics(
  2932. MetricsEnhancedPerformanceTestCase
  2933. ):
  2934. viewname = "sentry-api-0-organization-events"
  2935. def setUp(self) -> None:
  2936. super().setUp()
  2937. self.url = reverse(self.viewname, kwargs={"organization_slug": self.organization.slug})
  2938. self.features = {"organizations:on-demand-metrics-extraction-widgets": True}
  2939. def _create_specs(
  2940. self, params: dict[str, Any], groupbys: list[str] | None = None
  2941. ) -> list[OnDemandMetricSpec]:
  2942. """Creates all specs based on the parameters that would be passed to the endpoint."""
  2943. specs = []
  2944. for field in params["field"]:
  2945. spec = OnDemandMetricSpec(
  2946. field=field,
  2947. query=params["query"],
  2948. environment=params.get("environment"),
  2949. groupbys=groupbys,
  2950. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2951. )
  2952. specs.append(spec)
  2953. return specs
  2954. def _make_on_demand_request(
  2955. self, params: dict[str, Any], extra_features: dict[str, bool] | None = None
  2956. ) -> Response:
  2957. """Ensures that the required parameters for an on-demand request are included."""
  2958. # Expected parameters for this helper function
  2959. params["dataset"] = "metricsEnhanced"
  2960. params["useOnDemandMetrics"] = "true"
  2961. params["onDemandType"] = "dynamic_query"
  2962. _features = {**self.features, **(extra_features or {})}
  2963. return self.do_request(params, features=_features)
  2964. def _assert_on_demand_response(
  2965. self,
  2966. response: Response,
  2967. expected_on_demand_query: bool | None = True,
  2968. expected_dataset: str | None = "metricsEnhanced",
  2969. ) -> None:
  2970. """Basic assertions for an on-demand request."""
  2971. assert response.status_code == 200, response.content
  2972. meta = response.data["meta"]
  2973. assert meta.get("isMetricsExtractedData", False) is expected_on_demand_query
  2974. assert meta["dataset"] == expected_dataset
  2975. def test_is_metrics_extracted_data_is_included(self) -> None:
  2976. params = {"field": ["count()"], "query": "transaction.duration:>=91", "yAxis": "count()"}
  2977. specs = self._create_specs(params)
  2978. for spec in specs:
  2979. self.store_on_demand_metric(1, spec=spec)
  2980. response = self._make_on_demand_request(params)
  2981. self._assert_on_demand_response(response)
  2982. def test_on_demand_user_misery(self) -> None:
  2983. user_misery_field = "user_misery(300)"
  2984. query = "transaction.duration:>=100"
  2985. # We store data for both specs, however, when the query builders try to query
  2986. # for the data it will not query on-demand data
  2987. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  2988. spec = OnDemandMetricSpec(
  2989. field=user_misery_field,
  2990. query=query,
  2991. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2992. # We only allow querying the function in the latest spec version,
  2993. # otherwise, the data returned by the endpoint would be 0.05
  2994. spec_version=spec_version,
  2995. )
  2996. tags = {"satisfaction": "miserable"}
  2997. self.store_on_demand_metric(1, spec=spec, additional_tags=tags, timestamp=self.min_ago)
  2998. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  2999. params = {"field": [user_misery_field], "project": self.project.id, "query": query}
  3000. self._create_specs(params)
  3001. # We expect it to be False because we're not using the extra feature flag
  3002. response = self._make_on_demand_request(params)
  3003. self._assert_on_demand_response(response, expected_on_demand_query=False)
  3004. # Since we're using the extra feature flag we expect user_misery to be an on-demand metric
  3005. response = self._make_on_demand_request(params, {SPEC_VERSION_TWO_FLAG: True})
  3006. self._assert_on_demand_response(response, expected_on_demand_query=True)
  3007. assert response.data["data"] == [{user_misery_field: user_misery_formula(1, 2)}]
  3008. def test_on_demand_user_misery_discover_split_with_widget_id_unsaved(self) -> None:
  3009. user_misery_field = "user_misery(300)"
  3010. query = "transaction.duration:>=100"
  3011. _, widget, __ = create_widget(["count()"], "", self.project, discover_widget_split=None)
  3012. # We store data for both specs, however, when the query builders try to query
  3013. # for the data it will not query on-demand data
  3014. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  3015. spec = OnDemandMetricSpec(
  3016. field=user_misery_field,
  3017. query=query,
  3018. spec_type=MetricSpecType.DYNAMIC_QUERY,
  3019. # We only allow querying the function in the latest spec version,
  3020. # otherwise, the data returned by the endpoint would be 0.05
  3021. spec_version=spec_version,
  3022. )
  3023. tags = {"satisfaction": "miserable"}
  3024. self.store_on_demand_metric(1, spec=spec, additional_tags=tags, timestamp=self.min_ago)
  3025. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  3026. params = {"field": [user_misery_field], "project": self.project.id, "query": query}
  3027. self._create_specs(params)
  3028. params["dashboardWidgetId"] = widget.id
  3029. # Since we're using the extra feature flag we expect user_misery to be an on-demand metric
  3030. with mock.patch.object(widget, "save") as mock_widget_save:
  3031. response = self._make_on_demand_request(params, {SPEC_VERSION_TWO_FLAG: True})
  3032. assert bool(mock_widget_save.assert_called_once)
  3033. self._assert_on_demand_response(response, expected_on_demand_query=True)
  3034. assert response.data["data"] == [{user_misery_field: user_misery_formula(1, 2)}]
  3035. def test_on_demand_user_misery_discover_split_with_widget_id_saved(self) -> None:
  3036. user_misery_field = "user_misery(300)"
  3037. query = "transaction.duration:>=100"
  3038. _, widget, __ = create_widget(
  3039. ["count()"],
  3040. "",
  3041. self.project,
  3042. discover_widget_split=DashboardWidgetTypes.TRANSACTION_LIKE, # Transactions like uses on-demand
  3043. )
  3044. # We store data for both specs, however, when the query builders try to query
  3045. # for the data it will not query on-demand data
  3046. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  3047. spec = OnDemandMetricSpec(
  3048. field=user_misery_field,
  3049. query=query,
  3050. spec_type=MetricSpecType.DYNAMIC_QUERY,
  3051. # We only allow querying the function in the latest spec version,
  3052. # otherwise, the data returned by the endpoint would be 0.05
  3053. spec_version=spec_version,
  3054. )
  3055. tags = {"satisfaction": "miserable"}
  3056. self.store_on_demand_metric(1, spec=spec, additional_tags=tags, timestamp=self.min_ago)
  3057. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  3058. params = {"field": [user_misery_field], "project": self.project.id, "query": query}
  3059. self._create_specs(params)
  3060. params["dashboardWidgetId"] = widget.id
  3061. # Since we're using the extra feature flag we expect user_misery to be an on-demand metric
  3062. with mock.patch.object(widget, "save") as mock_widget_save:
  3063. response = self._make_on_demand_request(params, {SPEC_VERSION_TWO_FLAG: True})
  3064. assert bool(mock_widget_save.assert_not_called)
  3065. self._assert_on_demand_response(response, expected_on_demand_query=True)
  3066. assert response.data["data"] == [{user_misery_field: user_misery_formula(1, 2)}]
  3067. def test_on_demand_count_unique(self):
  3068. field = "count_unique(user)"
  3069. query = "transaction.duration:>0"
  3070. params = {"field": [field], "query": query}
  3071. # We do not really have to create the metrics for both specs since
  3072. # the first API call will not query any on-demand metric
  3073. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  3074. spec = OnDemandMetricSpec(
  3075. field=field,
  3076. query=query,
  3077. spec_type=MetricSpecType.DYNAMIC_QUERY,
  3078. spec_version=spec_version,
  3079. )
  3080. self.store_on_demand_metric(1, spec=spec, timestamp=self.min_ago)
  3081. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  3082. # The first call will not be on-demand
  3083. response = self._make_on_demand_request(params)
  3084. self._assert_on_demand_response(response, expected_on_demand_query=False)
  3085. # This second call will be on-demand
  3086. response = self._make_on_demand_request(
  3087. params, extra_features={SPEC_VERSION_TWO_FLAG: True}
  3088. )
  3089. self._assert_on_demand_response(response, expected_on_demand_query=True)
  3090. assert response.data["data"] == [{"count_unique(user)": 2}]
  3091. @region_silo_test
  3092. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  3093. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  3094. ):
  3095. def setUp(self):
  3096. super().setUp()
  3097. self.features["organizations:use-metrics-layer"] = True
  3098. @pytest.mark.xfail(reason="Not supported")
  3099. def test_time_spent(self):
  3100. super().test_time_spent()
  3101. @pytest.mark.xfail(reason="Not supported")
  3102. def test_http_error_rate(self):
  3103. super().test_http_error_rate()
  3104. @pytest.mark.xfail(reason="Multiple aliases to same column not supported")
  3105. def test_title_and_transaction_alias(self):
  3106. super().test_title_and_transaction_alias()
  3107. @pytest.mark.xfail(reason="Sort order is flaking when querying multiple datasets")
  3108. def test_maintain_sort_order_across_datasets(self):
  3109. """You may need to run this test a few times to get it to fail"""
  3110. super().test_maintain_sort_order_across_datasets()
  3111. @pytest.mark.xfail(reason="Not implemented")
  3112. def test_avg_compare(self):
  3113. super().test_avg_compare()
  3114. @pytest.mark.xfail(reason="Not implemented")
  3115. def test_avg_if(self):
  3116. super().test_avg_if()
  3117. @pytest.mark.xfail(reason="Not implemented")
  3118. def test_device_class(self):
  3119. super().test_device_class()
  3120. @pytest.mark.xfail(reason="Not implemented")
  3121. def test_device_class_filter(self):
  3122. super().test_device_class_filter()
  3123. @pytest.mark.xfail(reason="Not implemented")
  3124. def test_performance_score(self):
  3125. super().test_performance_score()
  3126. @pytest.mark.xfail(reason="Not implemented")
  3127. def test_performance_score_boundaries(self):
  3128. super().test_performance_score()
  3129. @pytest.mark.xfail(reason="Not implemented")
  3130. def test_weighted_performance_score(self):
  3131. super().test_weighted_performance_score()
  3132. @pytest.mark.xfail(reason="Not implemented")
  3133. def test_invalid_performance_score_column(self):
  3134. super().test_invalid_performance_score_column()
  3135. @pytest.mark.xfail(reason="Not implemented")
  3136. def test_invalid_weighted_performance_score_column(self):
  3137. super().test_invalid_weighted_performance_score_column()
  3138. @pytest.mark.xfail(reason="Not implemented")
  3139. def test_no_weighted_performance_score_column(self):
  3140. super().test_invalid_weighted_performance_score_column()
  3141. @pytest.mark.xfail(reason="Not implemented")
  3142. def test_opportunity_score(self):
  3143. super().test_opportunity_score()
  3144. @pytest.mark.xfail(reason="Not implemented")
  3145. def test_count_scores(self):
  3146. super().test_count_scores()
  3147. @pytest.mark.xfail(reason="Not implemented")
  3148. def test_count_starts(self):
  3149. super().test_count_starts()
  3150. @pytest.mark.xfail(reason="Not implemented")
  3151. def test_count_starts_returns_all_counts_when_no_arg_is_passed(self):
  3152. super().test_count_starts_returns_all_counts_when_no_arg_is_passed()
  3153. @pytest.mark.xfail(reason="Not implemented")
  3154. def test_timestamp_groupby(self):
  3155. super().test_timestamp_groupby()
  3156. @pytest.mark.xfail(reason="Not implemented")
  3157. def test_on_demand_with_mep(self):
  3158. super().test_on_demand_with_mep()