test_organization_events_mep.py 121 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450
  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. response = self.do_request(
  2402. {
  2403. "field": [
  2404. "transaction",
  2405. "performance_score(measurements.score.lcp)",
  2406. "performance_score(measurements.score.fcp)",
  2407. "performance_score(measurements.score.fid)",
  2408. "performance_score(measurements.score.ttfb)",
  2409. ],
  2410. "query": "event.type:transaction",
  2411. "dataset": "metrics",
  2412. "per_page": 50,
  2413. }
  2414. )
  2415. assert response.status_code == 200, response.content
  2416. assert len(response.data["data"]) == 1
  2417. data = response.data["data"]
  2418. meta = response.data["meta"]
  2419. field_meta = meta["fields"]
  2420. assert data[0]["performance_score(measurements.score.lcp)"] == 0.7923076923076923
  2421. assert data[0]["performance_score(measurements.score.fcp)"] == 0.5
  2422. assert data[0]["performance_score(measurements.score.fid)"] == 0
  2423. assert data[0]["performance_score(measurements.score.ttfb)"] == 0
  2424. assert meta["isMetricsData"]
  2425. assert field_meta["performance_score(measurements.score.lcp)"] == "number"
  2426. def test_performance_score_boundaries(self):
  2427. # Scores shouldn't exceed 1 or go below 0, but we can test these boundaries anyways
  2428. self.store_transaction_metric(
  2429. 0.65,
  2430. metric="measurements.score.lcp",
  2431. tags={"transaction": "foo_transaction"},
  2432. timestamp=self.min_ago,
  2433. )
  2434. self.store_transaction_metric(
  2435. 0.30,
  2436. metric="measurements.score.weight.lcp",
  2437. tags={"transaction": "foo_transaction"},
  2438. timestamp=self.min_ago,
  2439. )
  2440. self.store_transaction_metric(
  2441. -0.35,
  2442. metric="measurements.score.fcp",
  2443. tags={"transaction": "foo_transaction"},
  2444. timestamp=self.min_ago,
  2445. )
  2446. self.store_transaction_metric(
  2447. 0.70,
  2448. metric="measurements.score.weight.fcp",
  2449. tags={"transaction": "foo_transaction"},
  2450. timestamp=self.min_ago,
  2451. )
  2452. self.store_transaction_metric(
  2453. 0.3,
  2454. metric="measurements.score.total",
  2455. tags={"transaction": "foo_transaction"},
  2456. timestamp=self.min_ago,
  2457. )
  2458. response = self.do_request(
  2459. {
  2460. "field": [
  2461. "transaction",
  2462. "performance_score(measurements.score.lcp)",
  2463. "performance_score(measurements.score.fcp)",
  2464. ],
  2465. "query": "event.type:transaction",
  2466. "dataset": "metrics",
  2467. "per_page": 50,
  2468. }
  2469. )
  2470. assert response.status_code == 200, response.content
  2471. assert len(response.data["data"]) == 1
  2472. data = response.data["data"]
  2473. meta = response.data["meta"]
  2474. field_meta = meta["fields"]
  2475. assert data[0]["performance_score(measurements.score.lcp)"] == 1.0
  2476. assert data[0]["performance_score(measurements.score.fcp)"] == 0.0
  2477. assert meta["isMetricsData"]
  2478. assert field_meta["performance_score(measurements.score.lcp)"] == "number"
  2479. def test_weighted_performance_score(self):
  2480. self.store_transaction_metric(
  2481. 0.03,
  2482. metric="measurements.score.lcp",
  2483. tags={"transaction": "foo_transaction"},
  2484. timestamp=self.min_ago,
  2485. )
  2486. self.store_transaction_metric(
  2487. 0.30,
  2488. metric="measurements.score.weight.lcp",
  2489. tags={"transaction": "foo_transaction"},
  2490. timestamp=self.min_ago,
  2491. )
  2492. self.store_transaction_metric(
  2493. 0.03,
  2494. metric="measurements.score.total",
  2495. tags={"transaction": "foo_transaction"},
  2496. timestamp=self.min_ago,
  2497. )
  2498. self.store_transaction_metric(
  2499. 1.00,
  2500. metric="measurements.score.lcp",
  2501. tags={"transaction": "foo_transaction"},
  2502. timestamp=self.min_ago,
  2503. )
  2504. self.store_transaction_metric(
  2505. 1.00,
  2506. metric="measurements.score.weight.lcp",
  2507. tags={"transaction": "foo_transaction"},
  2508. timestamp=self.min_ago,
  2509. )
  2510. self.store_transaction_metric(
  2511. 1.00,
  2512. metric="measurements.score.total",
  2513. tags={"transaction": "foo_transaction"},
  2514. timestamp=self.min_ago,
  2515. )
  2516. self.store_transaction_metric(
  2517. 0.00,
  2518. metric="measurements.score.total",
  2519. tags={"transaction": "foo_transaction"},
  2520. timestamp=self.min_ago,
  2521. )
  2522. response = self.do_request(
  2523. {
  2524. "field": [
  2525. "transaction",
  2526. "weighted_performance_score(measurements.score.lcp)",
  2527. ],
  2528. "query": "event.type:transaction",
  2529. "dataset": "metrics",
  2530. "per_page": 50,
  2531. }
  2532. )
  2533. assert response.status_code == 200, response.content
  2534. assert len(response.data["data"]) == 1
  2535. data = response.data["data"]
  2536. meta = response.data["meta"]
  2537. field_meta = meta["fields"]
  2538. assert data[0]["weighted_performance_score(measurements.score.lcp)"] == 0.3433333333333333
  2539. assert meta["isMetricsData"]
  2540. assert field_meta["weighted_performance_score(measurements.score.lcp)"] == "number"
  2541. def test_invalid_performance_score_column(self):
  2542. self.store_transaction_metric(
  2543. 0.03,
  2544. metric="measurements.score.total",
  2545. tags={"transaction": "foo_transaction"},
  2546. timestamp=self.min_ago,
  2547. )
  2548. response = self.do_request(
  2549. {
  2550. "field": [
  2551. "transaction",
  2552. "performance_score(measurements.score.fp)",
  2553. ],
  2554. "query": "event.type:transaction",
  2555. "dataset": "metrics",
  2556. "per_page": 50,
  2557. }
  2558. )
  2559. assert response.status_code == 400, response.content
  2560. def test_invalid_weighted_performance_score_column(self):
  2561. self.store_transaction_metric(
  2562. 0.03,
  2563. metric="measurements.score.total",
  2564. tags={"transaction": "foo_transaction"},
  2565. timestamp=self.min_ago,
  2566. )
  2567. response = self.do_request(
  2568. {
  2569. "field": [
  2570. "transaction",
  2571. "weighted_performance_score(measurements.score.fp)",
  2572. ],
  2573. "query": "event.type:transaction",
  2574. "dataset": "metrics",
  2575. "per_page": 50,
  2576. }
  2577. )
  2578. assert response.status_code == 400, response.content
  2579. def test_no_weighted_performance_score_column(self):
  2580. self.store_transaction_metric(
  2581. 0.0,
  2582. metric="measurements.score.lcp",
  2583. tags={"transaction": "foo_transaction"},
  2584. timestamp=self.min_ago,
  2585. )
  2586. response = self.do_request(
  2587. {
  2588. "field": [
  2589. "transaction",
  2590. "weighted_performance_score(measurements.score.lcp)",
  2591. ],
  2592. "query": "event.type:transaction",
  2593. "dataset": "metrics",
  2594. "per_page": 50,
  2595. }
  2596. )
  2597. assert response.status_code == 200, response.content
  2598. assert len(response.data["data"]) == 1
  2599. data = response.data["data"]
  2600. meta = response.data["meta"]
  2601. field_meta = meta["fields"]
  2602. assert data[0]["weighted_performance_score(measurements.score.lcp)"] == 0.0
  2603. assert meta["isMetricsData"]
  2604. assert field_meta["weighted_performance_score(measurements.score.lcp)"] == "number"
  2605. def test_opportunity_score(self):
  2606. self.store_transaction_metric(
  2607. 0.03,
  2608. metric="measurements.score.lcp",
  2609. tags={"transaction": "foo_transaction"},
  2610. timestamp=self.min_ago,
  2611. )
  2612. self.store_transaction_metric(
  2613. 0.30,
  2614. metric="measurements.score.weight.lcp",
  2615. tags={"transaction": "foo_transaction"},
  2616. timestamp=self.min_ago,
  2617. )
  2618. self.store_transaction_metric(
  2619. 0.40,
  2620. metric="measurements.score.fcp",
  2621. tags={"transaction": "foo_transaction"},
  2622. timestamp=self.min_ago,
  2623. )
  2624. self.store_transaction_metric(
  2625. 0.70,
  2626. metric="measurements.score.weight.fcp",
  2627. tags={"transaction": "foo_transaction"},
  2628. timestamp=self.min_ago,
  2629. )
  2630. self.store_transaction_metric(
  2631. 0.43,
  2632. metric="measurements.score.total",
  2633. tags={"transaction": "foo_transaction"},
  2634. timestamp=self.min_ago,
  2635. )
  2636. self.store_transaction_metric(
  2637. 1.0,
  2638. metric="measurements.score.lcp",
  2639. tags={"transaction": "foo_transaction"},
  2640. timestamp=self.min_ago,
  2641. )
  2642. self.store_transaction_metric(
  2643. 1.0,
  2644. metric="measurements.score.weight.lcp",
  2645. tags={"transaction": "foo_transaction"},
  2646. timestamp=self.min_ago,
  2647. )
  2648. self.store_transaction_metric(
  2649. 1.0,
  2650. metric="measurements.score.total",
  2651. tags={"transaction": "foo_transaction"},
  2652. timestamp=self.min_ago,
  2653. )
  2654. self.store_transaction_metric(
  2655. 0.0,
  2656. metric="measurements.score.total",
  2657. tags={"transaction": "foo_transaction"},
  2658. timestamp=self.min_ago,
  2659. )
  2660. response = self.do_request(
  2661. {
  2662. "field": [
  2663. "transaction",
  2664. "opportunity_score(measurements.score.lcp)",
  2665. "opportunity_score(measurements.score.total)",
  2666. ],
  2667. "query": "event.type:transaction",
  2668. "dataset": "metrics",
  2669. "per_page": 50,
  2670. }
  2671. )
  2672. assert response.status_code == 200, response.content
  2673. assert len(response.data["data"]) == 1
  2674. data = response.data["data"]
  2675. meta = response.data["meta"]
  2676. assert data[0]["opportunity_score(measurements.score.lcp)"] == 0.27
  2677. assert data[0]["opportunity_score(measurements.score.total)"] == 1.57
  2678. assert meta["isMetricsData"]
  2679. def test_count_scores(self):
  2680. self.store_transaction_metric(
  2681. 0.1,
  2682. metric="measurements.score.total",
  2683. tags={"transaction": "foo_transaction"},
  2684. timestamp=self.min_ago,
  2685. )
  2686. self.store_transaction_metric(
  2687. 0.2,
  2688. metric="measurements.score.total",
  2689. tags={"transaction": "foo_transaction"},
  2690. timestamp=self.min_ago,
  2691. )
  2692. self.store_transaction_metric(
  2693. 0.3,
  2694. metric="measurements.score.total",
  2695. tags={"transaction": "foo_transaction"},
  2696. timestamp=self.min_ago,
  2697. )
  2698. self.store_transaction_metric(
  2699. 0.4,
  2700. metric="measurements.score.total",
  2701. tags={"transaction": "foo_transaction"},
  2702. timestamp=self.min_ago,
  2703. )
  2704. self.store_transaction_metric(
  2705. 0.5,
  2706. metric="measurements.score.lcp",
  2707. tags={"transaction": "foo_transaction"},
  2708. timestamp=self.min_ago,
  2709. )
  2710. response = self.do_request(
  2711. {
  2712. "field": [
  2713. "transaction",
  2714. "count_scores(measurements.score.total)",
  2715. "count_scores(measurements.score.lcp)",
  2716. ],
  2717. "query": "event.type:transaction",
  2718. "dataset": "metrics",
  2719. "per_page": 50,
  2720. }
  2721. )
  2722. assert response.status_code == 200, response.content
  2723. assert len(response.data["data"]) == 1
  2724. data = response.data["data"]
  2725. meta = response.data["meta"]
  2726. assert data[0]["count_scores(measurements.score.total)"] == 4
  2727. assert data[0]["count_scores(measurements.score.lcp)"] == 1
  2728. assert meta["isMetricsData"]
  2729. def test_count_starts(self):
  2730. self.store_transaction_metric(
  2731. 200,
  2732. metric="measurements.app_start_warm",
  2733. tags={"transaction": "foo_transaction"},
  2734. timestamp=self.min_ago,
  2735. )
  2736. self.store_transaction_metric(
  2737. 100,
  2738. metric="measurements.app_start_warm",
  2739. tags={"transaction": "foo_transaction"},
  2740. timestamp=self.min_ago,
  2741. )
  2742. self.store_transaction_metric(
  2743. 10,
  2744. metric="measurements.app_start_cold",
  2745. tags={"transaction": "foo_transaction"},
  2746. timestamp=self.min_ago,
  2747. )
  2748. response = self.do_request(
  2749. {
  2750. "field": [
  2751. "transaction",
  2752. "count_starts(measurements.app_start_warm)",
  2753. "count_starts(measurements.app_start_cold)",
  2754. ],
  2755. "query": "event.type:transaction",
  2756. "dataset": "metrics",
  2757. "per_page": 50,
  2758. }
  2759. )
  2760. assert response.status_code == 200, response.content
  2761. assert len(response.data["data"]) == 1
  2762. data = response.data["data"]
  2763. meta = response.data["meta"]
  2764. assert data[0]["count_starts(measurements.app_start_warm)"] == 2
  2765. assert data[0]["count_starts(measurements.app_start_cold)"] == 1
  2766. assert meta["isMetricsData"]
  2767. def test_count_starts_returns_all_counts_when_no_arg_is_passed(self):
  2768. self.store_transaction_metric(
  2769. 200,
  2770. metric="measurements.app_start_warm",
  2771. tags={"transaction": "foo_transaction"},
  2772. timestamp=self.min_ago,
  2773. )
  2774. self.store_transaction_metric(
  2775. 100,
  2776. metric="measurements.app_start_warm",
  2777. tags={"transaction": "foo_transaction"},
  2778. timestamp=self.min_ago,
  2779. )
  2780. self.store_transaction_metric(
  2781. 10,
  2782. metric="measurements.app_start_cold",
  2783. tags={"transaction": "foo_transaction"},
  2784. timestamp=self.min_ago,
  2785. )
  2786. response = self.do_request(
  2787. {
  2788. "field": [
  2789. "transaction",
  2790. "count_total_starts()",
  2791. ],
  2792. "query": "event.type:transaction",
  2793. "dataset": "metrics",
  2794. "per_page": 50,
  2795. }
  2796. )
  2797. assert response.status_code == 200, response.content
  2798. assert len(response.data["data"]) == 1
  2799. data = response.data["data"]
  2800. meta = response.data["meta"]
  2801. assert data[0]["count_total_starts()"] == 3
  2802. assert meta["isMetricsData"]
  2803. def test_timestamp_groupby(self):
  2804. self.store_transaction_metric(
  2805. 0.03,
  2806. tags={"transaction": "foo_transaction", "user": "foo"},
  2807. timestamp=self.min_ago,
  2808. )
  2809. response = self.do_request(
  2810. {
  2811. "field": [
  2812. "transaction",
  2813. "timestamp",
  2814. "count()",
  2815. "count_unique(user)",
  2816. ],
  2817. "query": "event.type:transaction",
  2818. "dataset": "metricsEnhanced",
  2819. "per_page": 50,
  2820. }
  2821. )
  2822. assert response.status_code == 200, response.content
  2823. assert len(response.data["data"]) == 1
  2824. data = response.data["data"]
  2825. meta = response.data["meta"]
  2826. assert data[0]["transaction"] == "foo_transaction"
  2827. assert meta["dataset"] == "metricsEnhanced"
  2828. def test_on_demand_with_mep(self):
  2829. # Store faketag as an OnDemandMetricSpec, which will put faketag into the metrics indexer
  2830. spec = OnDemandMetricSpec(
  2831. field="count()",
  2832. query="user.email:blah@example.com",
  2833. environment="prod",
  2834. groupbys=["faketag"],
  2835. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2836. )
  2837. self.store_on_demand_metric(123, spec=spec)
  2838. # This is the event that we should actually return
  2839. transaction_data = load_data("transaction", timestamp=self.min_ago)
  2840. transaction_data["tags"].append(("faketag", "foo"))
  2841. self.store_event(transaction_data, self.project.id)
  2842. with self.feature({"organizations:mep-use-default-tags": True}):
  2843. response = self.do_request(
  2844. {
  2845. "field": [
  2846. "faketag",
  2847. "count()",
  2848. ],
  2849. "query": "event.type:transaction",
  2850. "dataset": "metricsEnhanced",
  2851. "per_page": 50,
  2852. }
  2853. )
  2854. assert response.status_code == 200, response.content
  2855. assert len(response.data["data"]) == 1
  2856. data = response.data["data"]
  2857. meta = response.data["meta"]
  2858. assert data[0]["faketag"] == "foo"
  2859. assert not meta["isMetricsData"]
  2860. @region_silo_test
  2861. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithOnDemandMetrics(
  2862. MetricsEnhancedPerformanceTestCase
  2863. ):
  2864. viewname = "sentry-api-0-organization-events"
  2865. def setUp(self) -> None:
  2866. super().setUp()
  2867. self.url = reverse(self.viewname, kwargs={"organization_slug": self.organization.slug})
  2868. self.features = {"organizations:on-demand-metrics-extraction-widgets": True}
  2869. def _create_specs(
  2870. self, params: dict[str, Any], groupbys: list[str] | None = None
  2871. ) -> list[OnDemandMetricSpec]:
  2872. """Creates all specs based on the parameters that would be passed to the endpoint."""
  2873. specs = []
  2874. for field in params["field"]:
  2875. spec = OnDemandMetricSpec(
  2876. field=field,
  2877. query=params["query"],
  2878. environment=params.get("environment"),
  2879. groupbys=groupbys,
  2880. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2881. )
  2882. specs.append(spec)
  2883. return specs
  2884. def _make_on_demand_request(
  2885. self, params: dict[str, Any], extra_features: dict[str, bool] | None = None
  2886. ) -> Response:
  2887. """Ensures that the required parameters for an on-demand request are included."""
  2888. # Expected parameters for this helper function
  2889. params["dataset"] = "metricsEnhanced"
  2890. params["useOnDemandMetrics"] = "true"
  2891. params["onDemandType"] = "dynamic_query"
  2892. _features = {**self.features, **(extra_features or {})}
  2893. return self.do_request(params, features=_features)
  2894. def _assert_on_demand_response(
  2895. self,
  2896. response: Response,
  2897. expected_on_demand_query: bool | None = True,
  2898. expected_dataset: str | None = "metricsEnhanced",
  2899. ) -> None:
  2900. """Basic assertions for an on-demand request."""
  2901. assert response.status_code == 200, response.content
  2902. meta = response.data["meta"]
  2903. assert meta.get("isMetricsExtractedData", False) is expected_on_demand_query
  2904. assert meta["dataset"] == expected_dataset
  2905. def test_is_metrics_extracted_data_is_included(self) -> None:
  2906. params = {"field": ["count()"], "query": "transaction.duration:>=91", "yAxis": "count()"}
  2907. specs = self._create_specs(params)
  2908. for spec in specs:
  2909. self.store_on_demand_metric(1, spec=spec)
  2910. response = self._make_on_demand_request(params)
  2911. self._assert_on_demand_response(response)
  2912. def test_on_demand_user_misery(self) -> None:
  2913. user_misery_field = "user_misery(300)"
  2914. query = "transaction.duration:>=100"
  2915. # We store data for both specs, however, when the query builders try to query
  2916. # for the data it will not query on-demand data
  2917. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  2918. spec = OnDemandMetricSpec(
  2919. field=user_misery_field,
  2920. query=query,
  2921. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2922. # We only allow querying the function in the latest spec version,
  2923. # otherwise, the data returned by the endpoint would be 0.05
  2924. spec_version=spec_version,
  2925. )
  2926. tags = {"satisfaction": "miserable"}
  2927. self.store_on_demand_metric(1, spec=spec, additional_tags=tags, timestamp=self.min_ago)
  2928. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  2929. params = {"field": [user_misery_field], "project": self.project.id, "query": query}
  2930. self._create_specs(params)
  2931. # We expect it to be False because we're not using the extra feature flag
  2932. response = self._make_on_demand_request(params)
  2933. self._assert_on_demand_response(response, expected_on_demand_query=False)
  2934. # Since we're using the extra feature flag we expect user_misery to be an on-demand metric
  2935. response = self._make_on_demand_request(params, {SPEC_VERSION_TWO_FLAG: True})
  2936. self._assert_on_demand_response(response, expected_on_demand_query=True)
  2937. assert response.data["data"] == [{user_misery_field: user_misery_formula(1, 2)}]
  2938. def test_on_demand_user_misery_discover_split_with_widget_id_unsaved(self) -> None:
  2939. user_misery_field = "user_misery(300)"
  2940. query = "transaction.duration:>=100"
  2941. _, widget, __ = create_widget(["count()"], "", self.project, discover_widget_split=None)
  2942. # We store data for both specs, however, when the query builders try to query
  2943. # for the data it will not query on-demand data
  2944. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  2945. spec = OnDemandMetricSpec(
  2946. field=user_misery_field,
  2947. query=query,
  2948. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2949. # We only allow querying the function in the latest spec version,
  2950. # otherwise, the data returned by the endpoint would be 0.05
  2951. spec_version=spec_version,
  2952. )
  2953. tags = {"satisfaction": "miserable"}
  2954. self.store_on_demand_metric(1, spec=spec, additional_tags=tags, timestamp=self.min_ago)
  2955. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  2956. params = {"field": [user_misery_field], "project": self.project.id, "query": query}
  2957. self._create_specs(params)
  2958. params["dashboardWidgetId"] = widget.id
  2959. # Since we're using the extra feature flag we expect user_misery to be an on-demand metric
  2960. with mock.patch.object(widget, "save") as mock_widget_save:
  2961. response = self._make_on_demand_request(params, {SPEC_VERSION_TWO_FLAG: True})
  2962. assert bool(mock_widget_save.assert_called_once)
  2963. self._assert_on_demand_response(response, expected_on_demand_query=True)
  2964. assert response.data["data"] == [{user_misery_field: user_misery_formula(1, 2)}]
  2965. def test_on_demand_user_misery_discover_split_with_widget_id_saved(self) -> None:
  2966. user_misery_field = "user_misery(300)"
  2967. query = "transaction.duration:>=100"
  2968. _, widget, __ = create_widget(
  2969. ["count()"],
  2970. "",
  2971. self.project,
  2972. discover_widget_split=DashboardWidgetTypes.TRANSACTION_LIKE, # Transactions like uses on-demand
  2973. )
  2974. # We store data for both specs, however, when the query builders try to query
  2975. # for the data it will not query on-demand data
  2976. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  2977. spec = OnDemandMetricSpec(
  2978. field=user_misery_field,
  2979. query=query,
  2980. spec_type=MetricSpecType.DYNAMIC_QUERY,
  2981. # We only allow querying the function in the latest spec version,
  2982. # otherwise, the data returned by the endpoint would be 0.05
  2983. spec_version=spec_version,
  2984. )
  2985. tags = {"satisfaction": "miserable"}
  2986. self.store_on_demand_metric(1, spec=spec, additional_tags=tags, timestamp=self.min_ago)
  2987. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  2988. params = {"field": [user_misery_field], "project": self.project.id, "query": query}
  2989. self._create_specs(params)
  2990. params["dashboardWidgetId"] = widget.id
  2991. # Since we're using the extra feature flag we expect user_misery to be an on-demand metric
  2992. with mock.patch.object(widget, "save") as mock_widget_save:
  2993. response = self._make_on_demand_request(params, {SPEC_VERSION_TWO_FLAG: True})
  2994. assert bool(mock_widget_save.assert_not_called)
  2995. self._assert_on_demand_response(response, expected_on_demand_query=True)
  2996. assert response.data["data"] == [{user_misery_field: user_misery_formula(1, 2)}]
  2997. def test_on_demand_count_unique(self):
  2998. field = "count_unique(user)"
  2999. query = "transaction.duration:>0"
  3000. params = {"field": [field], "query": query}
  3001. # We do not really have to create the metrics for both specs since
  3002. # the first API call will not query any on-demand metric
  3003. for spec_version in OnDemandMetricSpecVersioning.get_spec_versions():
  3004. spec = OnDemandMetricSpec(
  3005. field=field,
  3006. query=query,
  3007. spec_type=MetricSpecType.DYNAMIC_QUERY,
  3008. spec_version=spec_version,
  3009. )
  3010. self.store_on_demand_metric(1, spec=spec, timestamp=self.min_ago)
  3011. self.store_on_demand_metric(2, spec=spec, timestamp=self.min_ago)
  3012. # The first call will not be on-demand
  3013. response = self._make_on_demand_request(params)
  3014. self._assert_on_demand_response(response, expected_on_demand_query=False)
  3015. # This second call will be on-demand
  3016. response = self._make_on_demand_request(
  3017. params, extra_features={SPEC_VERSION_TWO_FLAG: True}
  3018. )
  3019. self._assert_on_demand_response(response, expected_on_demand_query=True)
  3020. assert response.data["data"] == [{"count_unique(user)": 2}]
  3021. @region_silo_test
  3022. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  3023. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  3024. ):
  3025. def setUp(self):
  3026. super().setUp()
  3027. self.features["organizations:use-metrics-layer"] = True
  3028. @pytest.mark.xfail(reason="Not supported")
  3029. def test_time_spent(self):
  3030. super().test_time_spent()
  3031. @pytest.mark.xfail(reason="Not supported")
  3032. def test_http_error_rate(self):
  3033. super().test_http_error_rate()
  3034. @pytest.mark.xfail(reason="Multiple aliases to same column not supported")
  3035. def test_title_and_transaction_alias(self):
  3036. super().test_title_and_transaction_alias()
  3037. @pytest.mark.xfail(reason="Sort order is flaking when querying multiple datasets")
  3038. def test_maintain_sort_order_across_datasets(self):
  3039. """You may need to run this test a few times to get it to fail"""
  3040. super().test_maintain_sort_order_across_datasets()
  3041. @pytest.mark.xfail(reason="Not implemented")
  3042. def test_avg_compare(self):
  3043. super().test_avg_compare()
  3044. @pytest.mark.xfail(reason="Not implemented")
  3045. def test_avg_if(self):
  3046. super().test_avg_if()
  3047. @pytest.mark.xfail(reason="Not implemented")
  3048. def test_device_class(self):
  3049. super().test_device_class()
  3050. @pytest.mark.xfail(reason="Not implemented")
  3051. def test_device_class_filter(self):
  3052. super().test_device_class_filter()
  3053. @pytest.mark.xfail(reason="Not implemented")
  3054. def test_performance_score(self):
  3055. super().test_performance_score()
  3056. @pytest.mark.xfail(reason="Not implemented")
  3057. def test_performance_score_boundaries(self):
  3058. super().test_performance_score()
  3059. @pytest.mark.xfail(reason="Not implemented")
  3060. def test_weighted_performance_score(self):
  3061. super().test_weighted_performance_score()
  3062. @pytest.mark.xfail(reason="Not implemented")
  3063. def test_invalid_performance_score_column(self):
  3064. super().test_invalid_performance_score_column()
  3065. @pytest.mark.xfail(reason="Not implemented")
  3066. def test_invalid_weighted_performance_score_column(self):
  3067. super().test_invalid_weighted_performance_score_column()
  3068. @pytest.mark.xfail(reason="Not implemented")
  3069. def test_no_weighted_performance_score_column(self):
  3070. super().test_invalid_weighted_performance_score_column()
  3071. @pytest.mark.xfail(reason="Not implemented")
  3072. def test_opportunity_score(self):
  3073. super().test_opportunity_score()
  3074. @pytest.mark.xfail(reason="Not implemented")
  3075. def test_count_scores(self):
  3076. super().test_count_scores()
  3077. @pytest.mark.xfail(reason="Not implemented")
  3078. def test_count_starts(self):
  3079. super().test_count_starts()
  3080. @pytest.mark.xfail(reason="Not implemented")
  3081. def test_count_starts_returns_all_counts_when_no_arg_is_passed(self):
  3082. super().test_count_starts_returns_all_counts_when_no_arg_is_passed()
  3083. @pytest.mark.xfail(reason="Not implemented")
  3084. def test_timestamp_groupby(self):
  3085. super().test_timestamp_groupby()
  3086. @pytest.mark.xfail(reason="Not implemented")
  3087. def test_on_demand_with_mep(self):
  3088. super().test_on_demand_with_mep()