test_organization_events_v2.py 220 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835
  1. from base64 import b64encode
  2. from unittest import mock
  3. import pytest
  4. from django.urls import reverse
  5. from django.utils import timezone
  6. from pytz import utc
  7. from snuba_sdk.column import Column
  8. from snuba_sdk.conditions import InvalidConditionError
  9. from snuba_sdk.function import Function
  10. from sentry.discover.models import TeamKeyTransaction
  11. from sentry.exceptions import IncompatibleMetricsQuery, InvalidSearchQuery
  12. from sentry.models import ApiKey, ProjectTeam, ProjectTransactionThreshold, ReleaseStages
  13. from sentry.models.transaction_threshold import (
  14. ProjectTransactionThresholdOverride,
  15. TransactionMetric,
  16. )
  17. from sentry.search.events import constants
  18. from sentry.testutils import APITestCase, MetricsEnhancedPerformanceTestCase, SnubaTestCase
  19. from sentry.testutils.helpers import parse_link_header
  20. from sentry.testutils.helpers.datetime import before_now, iso_format
  21. from sentry.testutils.skips import requires_not_arm64
  22. from sentry.utils import json
  23. from sentry.utils.samples import load_data
  24. from sentry.utils.snuba import QueryExecutionError, QueryIllegalTypeOfArgument, RateLimitExceeded
  25. MAX_QUERYABLE_TRANSACTION_THRESHOLDS = 1
  26. class OrganizationEventsV2EndpointTest(APITestCase, SnubaTestCase):
  27. def setUp(self):
  28. super().setUp()
  29. self.min_ago = iso_format(before_now(minutes=1))
  30. self.two_min_ago = iso_format(before_now(minutes=2))
  31. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  32. self.features = {}
  33. def do_request(self, query, features=None):
  34. if features is None:
  35. features = {"organizations:discover-basic": True}
  36. features.update(self.features)
  37. self.login_as(user=self.user)
  38. url = reverse(
  39. "sentry-api-0-organization-eventsv2",
  40. kwargs={"organization_slug": self.organization.slug},
  41. )
  42. with self.feature(features):
  43. return self.client.get(url, query, format="json")
  44. def test_no_projects(self):
  45. response = self.do_request({})
  46. assert response.status_code == 200, response.content
  47. assert len(response.data) == 0
  48. def test_api_key_request(self):
  49. project = self.create_project()
  50. self.store_event(
  51. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  52. project_id=project.id,
  53. )
  54. # Project ID cannot be inffered when using an org API key, so that must
  55. # be passed in the parameters
  56. api_key = ApiKey.objects.create(organization=self.organization, scope_list=["org:read"])
  57. query = {"field": ["project.name", "environment"], "project": [project.id]}
  58. url = reverse(
  59. "sentry-api-0-organization-eventsv2",
  60. kwargs={"organization_slug": self.organization.slug},
  61. )
  62. response = self.client.get(
  63. url,
  64. query,
  65. format="json",
  66. HTTP_AUTHORIZATION=b"Basic " + b64encode(f"{api_key.key}:".encode()),
  67. )
  68. assert response.status_code == 200, response.content
  69. assert len(response.data["data"]) == 1
  70. assert response.data["data"][0]["project.name"] == project.slug
  71. def test_performance_view_feature(self):
  72. self.store_event(
  73. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  74. project_id=self.project.id,
  75. )
  76. query = {"field": ["id", "project.id"], "project": [self.project.id]}
  77. response = self.do_request(query)
  78. assert response.status_code == 200
  79. assert len(response.data["data"]) == 1
  80. def test_multi_project_feature_gate_rejection(self):
  81. team = self.create_team(organization=self.organization, members=[self.user])
  82. project = self.create_project(organization=self.organization, teams=[team])
  83. project2 = self.create_project(organization=self.organization, teams=[team])
  84. self.store_event(
  85. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  86. project_id=project.id,
  87. )
  88. self.store_event(
  89. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  90. project_id=project2.id,
  91. )
  92. query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
  93. response = self.do_request(query)
  94. assert response.status_code == 400
  95. assert "events from multiple projects" in response.data["detail"]
  96. def test_invalid_search_terms(self):
  97. project = self.create_project()
  98. self.store_event(
  99. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  100. project_id=project.id,
  101. )
  102. query = {"field": ["id"], "query": "hi \n there"}
  103. response = self.do_request(query)
  104. assert response.status_code == 400, response.content
  105. assert (
  106. response.data["detail"]
  107. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  108. )
  109. def test_invalid_trace_span(self):
  110. project = self.create_project()
  111. self.store_event(
  112. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  113. project_id=project.id,
  114. )
  115. query = {"field": ["id"], "query": "trace.span:invalid"}
  116. response = self.do_request(query)
  117. assert response.status_code == 400, response.content
  118. assert (
  119. response.data["detail"]
  120. == "trace.span must be a valid 16 character hex (containing only digits, or a-f characters)"
  121. )
  122. query = {"field": ["id"], "query": "trace.parent_span:invalid"}
  123. response = self.do_request(query)
  124. assert response.status_code == 400, response.content
  125. assert (
  126. response.data["detail"]
  127. == "trace.parent_span must be a valid 16 character hex (containing only digits, or a-f characters)"
  128. )
  129. query = {"field": ["id"], "query": "trace.span:*"}
  130. response = self.do_request(query)
  131. assert response.status_code == 400, response.content
  132. assert (
  133. response.data["detail"] == "Wildcard conditions are not permitted on `trace.span` field"
  134. )
  135. query = {"field": ["id"], "query": "trace.parent_span:*"}
  136. response = self.do_request(query)
  137. assert response.status_code == 400, response.content
  138. assert (
  139. response.data["detail"]
  140. == "Wildcard conditions are not permitted on `trace.parent_span` field"
  141. )
  142. @mock.patch("sentry.snuba.discover.raw_query")
  143. @mock.patch("sentry.search.events.builder.raw_snql_query")
  144. def test_handling_snuba_errors(self, mock_snql_query, mock_query):
  145. mock_query.side_effect = RateLimitExceeded("test")
  146. mock_snql_query.side_effect = RateLimitExceeded("test")
  147. project = self.create_project()
  148. self.store_event(
  149. data={"event_id": "a" * 32, "message": "how to make fast"}, project_id=project.id
  150. )
  151. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  152. response = self.do_request(query)
  153. assert response.status_code == 400, response.content
  154. assert response.data["detail"] == constants.TIMEOUT_ERROR_MESSAGE
  155. mock_query.side_effect = QueryExecutionError("test")
  156. mock_snql_query.side_effect = QueryExecutionError("test")
  157. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  158. response = self.do_request(query)
  159. assert response.status_code == 500, response.content
  160. assert response.data["detail"] == "Internal error. Your query failed to run."
  161. mock_query.side_effect = QueryIllegalTypeOfArgument("test")
  162. mock_snql_query.side_effect = QueryIllegalTypeOfArgument("test")
  163. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  164. response = self.do_request(query)
  165. assert response.status_code == 400, response.content
  166. assert response.data["detail"] == "Invalid query. Argument to function is wrong type."
  167. def test_out_of_retention(self):
  168. self.create_project()
  169. with self.options({"system.event-retention-days": 10}):
  170. query = {
  171. "field": ["id", "timestamp"],
  172. "orderby": ["-timestamp", "-id"],
  173. "start": iso_format(before_now(days=20)),
  174. "end": iso_format(before_now(days=15)),
  175. }
  176. response = self.do_request(query)
  177. assert response.status_code == 400, response.content
  178. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  179. def test_raw_data(self):
  180. project = self.create_project()
  181. self.store_event(
  182. data={
  183. "event_id": "a" * 32,
  184. "environment": "staging",
  185. "timestamp": self.two_min_ago,
  186. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  187. },
  188. project_id=project.id,
  189. )
  190. self.store_event(
  191. data={
  192. "event_id": "b" * 32,
  193. "environment": "staging",
  194. "timestamp": self.min_ago,
  195. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  196. },
  197. project_id=project.id,
  198. )
  199. query = {
  200. "field": ["id", "project.id", "user.email", "user.ip", "timestamp"],
  201. "orderby": "-timestamp",
  202. }
  203. response = self.do_request(query)
  204. assert response.status_code == 200, response.content
  205. data = response.data["data"]
  206. assert len(data) == 2
  207. assert data[0]["id"] == "b" * 32
  208. assert data[0]["project.id"] == project.id
  209. assert data[0]["user.email"] == "foo@example.com"
  210. assert "project.name" not in data[0], "project.id does not auto select name"
  211. assert "project" not in data[0]
  212. meta = response.data["meta"]
  213. assert meta["id"] == "string"
  214. assert meta["user.email"] == "string"
  215. assert meta["user.ip"] == "string"
  216. assert meta["timestamp"] == "date"
  217. def test_project_name(self):
  218. project = self.create_project()
  219. self.store_event(
  220. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  221. project_id=project.id,
  222. )
  223. query = {"field": ["project.name", "environment"]}
  224. response = self.do_request(query)
  225. assert response.status_code == 200, response.content
  226. assert len(response.data["data"]) == 1
  227. assert response.data["data"][0]["project.name"] == project.slug
  228. assert "project.id" not in response.data["data"][0]
  229. assert response.data["data"][0]["environment"] == "staging"
  230. def test_project_without_name(self):
  231. project = self.create_project()
  232. self.store_event(
  233. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  234. project_id=project.id,
  235. )
  236. query = {"field": ["project", "environment"]}
  237. response = self.do_request(query)
  238. assert response.status_code == 200, response.content
  239. assert len(response.data["data"]) == 1
  240. assert response.data["data"][0]["project"] == project.slug
  241. assert response.data["meta"]["project"] == "string"
  242. assert "project.id" not in response.data["data"][0]
  243. assert response.data["data"][0]["environment"] == "staging"
  244. def test_project_in_query(self):
  245. project = self.create_project()
  246. self.store_event(
  247. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  248. project_id=project.id,
  249. )
  250. query = {
  251. "field": ["project", "count()"],
  252. "query": 'project:"%s"' % project.slug,
  253. "statsPeriod": "14d",
  254. }
  255. response = self.do_request(query)
  256. assert response.status_code == 200, response.content
  257. assert len(response.data["data"]) == 1
  258. assert response.data["data"][0]["project"] == project.slug
  259. assert "project.id" not in response.data["data"][0]
  260. def test_project_in_query_not_in_header(self):
  261. project = self.create_project()
  262. other_project = self.create_project()
  263. self.store_event(
  264. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  265. project_id=project.id,
  266. )
  267. query = {
  268. "field": ["project", "count()"],
  269. "query": 'project:"%s"' % project.slug,
  270. "statsPeriod": "14d",
  271. "project": other_project.id,
  272. }
  273. response = self.do_request(query)
  274. assert response.status_code == 400, response.content
  275. assert (
  276. response.data["detail"]
  277. == f"Invalid query. Project(s) {project.slug} do not exist or are not actively selected."
  278. )
  279. def test_project_in_query_does_not_exist(self):
  280. project = self.create_project()
  281. self.store_event(
  282. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  283. project_id=project.id,
  284. )
  285. query = {"field": ["project", "count()"], "query": "project:morty", "statsPeriod": "14d"}
  286. response = self.do_request(query)
  287. assert response.status_code == 400, response.content
  288. assert (
  289. response.data["detail"]
  290. == "Invalid query. Project(s) morty do not exist or are not actively selected."
  291. )
  292. def test_not_project_in_query_but_in_header(self):
  293. team = self.create_team(organization=self.organization, members=[self.user])
  294. project = self.create_project(organization=self.organization, teams=[team])
  295. project2 = self.create_project(organization=self.organization, teams=[team])
  296. self.store_event(
  297. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  298. project_id=project.id,
  299. )
  300. self.store_event(
  301. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  302. project_id=project2.id,
  303. )
  304. query = {
  305. "field": ["id", "project.id"],
  306. "project": [project.id],
  307. "query": f"!project:{project2.slug}",
  308. }
  309. response = self.do_request(query)
  310. assert response.status_code == 200
  311. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  312. def test_not_project_in_query_with_all_projects(self):
  313. team = self.create_team(organization=self.organization, members=[self.user])
  314. project = self.create_project(organization=self.organization, teams=[team])
  315. project2 = self.create_project(organization=self.organization, teams=[team])
  316. self.store_event(
  317. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  318. project_id=project.id,
  319. )
  320. self.store_event(
  321. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  322. project_id=project2.id,
  323. )
  324. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  325. query = {
  326. "field": ["id", "project.id"],
  327. "project": [-1],
  328. "query": f"!project:{project2.slug}",
  329. }
  330. response = self.do_request(query, features=features)
  331. assert response.status_code == 200
  332. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  333. def test_project_condition_used_for_automatic_filters(self):
  334. project = self.create_project()
  335. self.store_event(
  336. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  337. project_id=project.id,
  338. )
  339. query = {
  340. "field": ["project", "count()"],
  341. "query": 'project:"%s"' % project.slug,
  342. "statsPeriod": "14d",
  343. }
  344. response = self.do_request(query)
  345. assert response.status_code == 200, response.content
  346. assert len(response.data["data"]) == 1
  347. assert response.data["data"][0]["project"] == project.slug
  348. assert "project.id" not in response.data["data"][0]
  349. def test_auto_insert_project_name_when_event_id_present(self):
  350. project = self.create_project()
  351. self.store_event(
  352. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  353. project_id=project.id,
  354. )
  355. query = {"field": ["id"], "statsPeriod": "1h"}
  356. response = self.do_request(query)
  357. assert response.status_code == 200, response.content
  358. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32}]
  359. def test_auto_insert_project_name_when_event_id_present_with_aggregate(self):
  360. project = self.create_project()
  361. self.store_event(
  362. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  363. project_id=project.id,
  364. )
  365. query = {"field": ["id", "count()"], "statsPeriod": "1h"}
  366. response = self.do_request(query)
  367. assert response.status_code == 200, response.content
  368. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32, "count": 1}]
  369. def test_user_search(self):
  370. project = self.create_project()
  371. data = load_data("transaction", timestamp=before_now(minutes=1))
  372. data["user"] = {
  373. "email": "foo@example.com",
  374. "id": "123",
  375. "ip_address": "127.0.0.1",
  376. "username": "foo",
  377. }
  378. self.store_event(data, project_id=project.id)
  379. fields = {
  380. "email": "user.email",
  381. "id": "user.id",
  382. "ip_address": "user.ip",
  383. "username": "user.username",
  384. }
  385. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  386. for key, value in data["user"].items():
  387. field = fields[key]
  388. query = {
  389. "field": ["project", "user"],
  390. "query": f"{field}:{value}",
  391. "statsPeriod": "14d",
  392. }
  393. response = self.do_request(query, features=features)
  394. assert response.status_code == 200, response.content
  395. assert len(response.data["data"]) == 1
  396. assert response.data["data"][0]["project"] == project.slug
  397. assert response.data["data"][0]["user"] == "id:123"
  398. def test_has_user(self):
  399. project = self.create_project()
  400. data = load_data("transaction", timestamp=before_now(minutes=1))
  401. self.store_event(data, project_id=project.id)
  402. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  403. for value in data["user"].values():
  404. query = {"field": ["project", "user"], "query": "has:user", "statsPeriod": "14d"}
  405. response = self.do_request(query, features=features)
  406. assert response.status_code == 200, response.content
  407. assert len(response.data["data"]) == 1
  408. assert response.data["data"][0]["user"] == "ip:{}".format(data["user"]["ip_address"])
  409. def test_team_param_no_access(self):
  410. org = self.create_organization(
  411. owner=self.user, # use other user as owner
  412. name="foo",
  413. flags=0, # disable default allow_joinleave
  414. )
  415. project = self.create_project(name="baz", organization=org)
  416. user = self.create_user()
  417. self.login_as(user=user, superuser=False)
  418. team = self.create_team(organization=org, name="Team Bar")
  419. project.add_team(team)
  420. self.store_event(
  421. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  422. project_id=project.id,
  423. )
  424. query = {"field": ["id", "project.id"], "project": [project.id], "team": [team.id]}
  425. response = self.do_request(query)
  426. assert response.status_code == 403, response.content
  427. assert response.data["detail"] == "You do not have permission to perform this action."
  428. def test_comparison_operators_on_numeric_field(self):
  429. project = self.create_project()
  430. event = self.store_event(
  431. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  432. )
  433. query = {"field": ["issue"], "query": f"issue.id:>{event.group.id - 1}"}
  434. response = self.do_request(query)
  435. assert response.status_code == 200, response.content
  436. assert len(response.data["data"]) == 1
  437. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  438. query = {"field": ["issue"], "query": f"issue.id:>{event.group.id}"}
  439. response = self.do_request(query)
  440. assert response.status_code == 200, response.content
  441. assert len(response.data["data"]) == 0
  442. def test_negation_on_numeric_field_excludes_issue(self):
  443. project = self.create_project()
  444. event = self.store_event(
  445. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  446. )
  447. query = {"field": ["issue"], "query": f"issue.id:{event.group.id}"}
  448. response = self.do_request(query)
  449. assert response.status_code == 200, response.content
  450. assert len(response.data["data"]) == 1
  451. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  452. query = {"field": ["issue"], "query": f"!issue.id:{event.group.id}"}
  453. response = self.do_request(query)
  454. assert response.status_code == 200, response.content
  455. assert len(response.data["data"]) == 0
  456. def test_negation_on_numeric_in_filter_excludes_issue(self):
  457. project = self.create_project()
  458. event = self.store_event(
  459. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  460. )
  461. query = {"field": ["issue"], "query": f"issue.id:[{event.group.id}]"}
  462. response = self.do_request(query)
  463. assert response.status_code == 200, response.content
  464. assert len(response.data["data"]) == 1
  465. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  466. query = {"field": ["issue"], "query": f"!issue.id:[{event.group.id}]"}
  467. response = self.do_request(query)
  468. assert response.status_code == 200, response.content
  469. assert len(response.data["data"]) == 0
  470. def test_negation_on_duration_filter_excludes_transaction(self):
  471. project = self.create_project()
  472. data = load_data("transaction", timestamp=before_now(minutes=1))
  473. event = self.store_event(data, project_id=project.id)
  474. duration = int(event.data.get("timestamp") - event.data.get("start_timestamp")) * 1000
  475. query = {"field": ["transaction"], "query": f"transaction.duration:{duration}"}
  476. response = self.do_request(query)
  477. assert response.status_code == 200, response.content
  478. assert len(response.data["data"]) == 1
  479. assert response.data["data"][0]["id"] == event.event_id
  480. query = {"field": ["transaction"], "query": f"!transaction.duration:{duration}"}
  481. response = self.do_request(query)
  482. assert response.status_code == 200, response.content
  483. assert len(response.data["data"]) == 0
  484. def test_has_issue(self):
  485. project = self.create_project()
  486. event = self.store_event(
  487. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  488. )
  489. data = load_data("transaction", timestamp=before_now(minutes=1))
  490. self.store_event(data, project_id=project.id)
  491. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  492. # should only show 1 event of type default
  493. query = {"field": ["project", "issue"], "query": "has:issue", "statsPeriod": "14d"}
  494. response = self.do_request(query, features=features)
  495. assert response.status_code == 200, response.content
  496. assert len(response.data["data"]) == 1
  497. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  498. # should only show 1 event of type default
  499. query = {
  500. "field": ["project", "issue"],
  501. "query": "event.type:default has:issue",
  502. "statsPeriod": "14d",
  503. }
  504. response = self.do_request(query, features=features)
  505. assert response.status_code == 200, response.content
  506. assert len(response.data["data"]) == 1
  507. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  508. # should show no results because no the default event has an issue
  509. query = {
  510. "field": ["project", "issue"],
  511. "query": "event.type:default !has:issue",
  512. "statsPeriod": "14d",
  513. }
  514. response = self.do_request(query, features=features)
  515. assert response.status_code == 200, response.content
  516. assert len(response.data["data"]) == 0
  517. # should show no results because no transactions have issues
  518. query = {
  519. "field": ["project", "issue"],
  520. "query": "event.type:transaction has:issue",
  521. "statsPeriod": "14d",
  522. }
  523. response = self.do_request(query, features=features)
  524. assert response.status_code == 200, response.content
  525. assert len(response.data["data"]) == 0
  526. # should only show 1 event of type transaction since they don't have issues
  527. query = {
  528. "field": ["project", "issue"],
  529. "query": "event.type:transaction !has:issue",
  530. "statsPeriod": "14d",
  531. }
  532. response = self.do_request(query, features=features)
  533. assert response.status_code == 200, response.content
  534. assert len(response.data["data"]) == 1
  535. assert response.data["data"][0]["issue"] == "unknown"
  536. @pytest.mark.skip("Cannot look up group_id of transaction events")
  537. def test_unknown_issue(self):
  538. project = self.create_project()
  539. event = self.store_event(
  540. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  541. )
  542. data = load_data("transaction", timestamp=before_now(minutes=1))
  543. self.store_event(data, project_id=project.id)
  544. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  545. query = {"field": ["project", "issue"], "query": "issue:unknown", "statsPeriod": "14d"}
  546. response = self.do_request(query, features=features)
  547. assert response.status_code == 200, response.content
  548. assert len(response.data["data"]) == 1
  549. assert response.data["data"][0]["issue"] == "unknown"
  550. query = {"field": ["project", "issue"], "query": "!issue:unknown", "statsPeriod": "14d"}
  551. response = self.do_request(query, features=features)
  552. assert response.status_code == 200, response.content
  553. assert len(response.data["data"]) == 1
  554. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  555. def test_negative_user_search(self):
  556. project = self.create_project()
  557. user_data = {"email": "foo@example.com", "id": "123", "username": "foo"}
  558. # Load an event with data that shouldn't match
  559. data = load_data("transaction", timestamp=before_now(minutes=1))
  560. data["transaction"] = "/transactions/nomatch"
  561. event_user = user_data.copy()
  562. event_user["id"] = "undefined"
  563. data["user"] = event_user
  564. self.store_event(data, project_id=project.id)
  565. # Load a matching event
  566. data = load_data("transaction", timestamp=before_now(minutes=1))
  567. data["transaction"] = "/transactions/matching"
  568. data["user"] = user_data
  569. self.store_event(data, project_id=project.id)
  570. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  571. query = {
  572. "field": ["project", "user"],
  573. "query": '!user:"id:undefined"',
  574. "statsPeriod": "14d",
  575. }
  576. response = self.do_request(query, features=features)
  577. assert response.status_code == 200, response.content
  578. assert len(response.data["data"]) == 1
  579. assert response.data["data"][0]["user"] == "id:{}".format(user_data["id"])
  580. assert "user.email" not in response.data["data"][0]
  581. assert "user.id" not in response.data["data"][0]
  582. def test_not_project_in_query(self):
  583. project1 = self.create_project()
  584. project2 = self.create_project()
  585. self.store_event(
  586. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  587. project_id=project1.id,
  588. )
  589. self.store_event(
  590. data={"event_id": "b" * 32, "environment": "staging", "timestamp": self.min_ago},
  591. project_id=project2.id,
  592. )
  593. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  594. query = {
  595. "field": ["project", "count()"],
  596. "query": '!project:"%s"' % project1.slug,
  597. "statsPeriod": "14d",
  598. }
  599. response = self.do_request(query, features=features)
  600. assert response.status_code == 200, response.content
  601. assert len(response.data["data"]) == 1
  602. assert response.data["data"][0]["project"] == project2.slug
  603. assert "project.id" not in response.data["data"][0]
  604. def test_error_handled_condition(self):
  605. self.login_as(user=self.user)
  606. project = self.create_project()
  607. prototype = load_data("android-ndk")
  608. events = (
  609. ("a" * 32, "not handled", False),
  610. ("b" * 32, "was handled", True),
  611. ("c" * 32, "undefined", None),
  612. )
  613. for event in events:
  614. prototype["event_id"] = event[0]
  615. prototype["message"] = event[1]
  616. prototype["exception"]["values"][0]["value"] = event[1]
  617. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  618. prototype["timestamp"] = self.two_min_ago
  619. self.store_event(data=prototype, project_id=project.id)
  620. with self.feature("organizations:discover-basic"):
  621. query = {
  622. "field": ["message", "error.handled"],
  623. "query": "error.handled:0",
  624. "orderby": "message",
  625. }
  626. response = self.do_request(query)
  627. assert response.status_code == 200, response.data
  628. assert 1 == len(response.data["data"])
  629. assert [0] == response.data["data"][0]["error.handled"]
  630. with self.feature("organizations:discover-basic"):
  631. query = {
  632. "field": ["message", "error.handled"],
  633. "query": "error.handled:1",
  634. "orderby": "message",
  635. }
  636. response = self.do_request(query)
  637. assert response.status_code == 200, response.data
  638. assert 2 == len(response.data["data"])
  639. assert [None] == response.data["data"][0]["error.handled"]
  640. assert [1] == response.data["data"][1]["error.handled"]
  641. def test_error_unhandled_condition(self):
  642. self.login_as(user=self.user)
  643. project = self.create_project()
  644. prototype = load_data("android-ndk")
  645. events = (
  646. ("a" * 32, "not handled", False),
  647. ("b" * 32, "was handled", True),
  648. ("c" * 32, "undefined", None),
  649. )
  650. for event in events:
  651. prototype["event_id"] = event[0]
  652. prototype["message"] = event[1]
  653. prototype["exception"]["values"][0]["value"] = event[1]
  654. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  655. prototype["timestamp"] = self.two_min_ago
  656. self.store_event(data=prototype, project_id=project.id)
  657. with self.feature("organizations:discover-basic"):
  658. query = {
  659. "field": ["message", "error.unhandled", "error.handled"],
  660. "query": "error.unhandled:true",
  661. "orderby": "message",
  662. }
  663. response = self.do_request(query)
  664. assert response.status_code == 200, response.data
  665. assert 1 == len(response.data["data"])
  666. assert [0] == response.data["data"][0]["error.handled"]
  667. assert 1 == response.data["data"][0]["error.unhandled"]
  668. with self.feature("organizations:discover-basic"):
  669. query = {
  670. "field": ["message", "error.handled", "error.unhandled"],
  671. "query": "error.unhandled:false",
  672. "orderby": "message",
  673. }
  674. response = self.do_request(query)
  675. assert response.status_code == 200, response.data
  676. assert 2 == len(response.data["data"])
  677. assert [None] == response.data["data"][0]["error.handled"]
  678. assert 0 == response.data["data"][0]["error.unhandled"]
  679. assert [1] == response.data["data"][1]["error.handled"]
  680. assert 0 == response.data["data"][1]["error.unhandled"]
  681. def test_implicit_groupby(self):
  682. project = self.create_project()
  683. self.store_event(
  684. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  685. project_id=project.id,
  686. )
  687. event1 = self.store_event(
  688. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_1"]},
  689. project_id=project.id,
  690. )
  691. event2 = self.store_event(
  692. data={"event_id": "c" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  693. project_id=project.id,
  694. )
  695. query = {"field": ["count(id)", "project.id", "issue.id"], "orderby": "issue.id"}
  696. response = self.do_request(query)
  697. assert response.status_code == 200, response.content
  698. assert len(response.data["data"]) == 2
  699. data = response.data["data"]
  700. assert data[0] == {"project.id": project.id, "issue.id": event1.group_id, "count_id": 2}
  701. assert data[1] == {"project.id": project.id, "issue.id": event2.group_id, "count_id": 1}
  702. meta = response.data["meta"]
  703. assert meta["count_id"] == "integer"
  704. def test_orderby(self):
  705. project = self.create_project()
  706. self.store_event(
  707. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  708. )
  709. self.store_event(
  710. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project.id
  711. )
  712. self.store_event(
  713. data={"event_id": "c" * 32, "timestamp": self.min_ago}, project_id=project.id
  714. )
  715. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  716. response = self.do_request(query)
  717. assert response.status_code == 200, response.content
  718. data = response.data["data"]
  719. assert data[0]["id"] == "c" * 32
  720. assert data[1]["id"] == "b" * 32
  721. assert data[2]["id"] == "a" * 32
  722. def test_sort_title(self):
  723. project = self.create_project()
  724. self.store_event(
  725. data={"event_id": "a" * 32, "message": "zlast", "timestamp": self.two_min_ago},
  726. project_id=project.id,
  727. )
  728. self.store_event(
  729. data={"event_id": "b" * 32, "message": "second", "timestamp": self.min_ago},
  730. project_id=project.id,
  731. )
  732. self.store_event(
  733. data={"event_id": "c" * 32, "message": "first", "timestamp": self.min_ago},
  734. project_id=project.id,
  735. )
  736. query = {"field": ["id", "title"], "sort": "title"}
  737. response = self.do_request(query)
  738. assert response.status_code == 200, response.content
  739. data = response.data["data"]
  740. assert data[0]["id"] == "c" * 32
  741. assert data[1]["id"] == "b" * 32
  742. assert data[2]["id"] == "a" * 32
  743. def test_sort_invalid(self):
  744. project = self.create_project()
  745. self.store_event(
  746. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  747. )
  748. query = {"field": ["id"], "sort": "garbage"}
  749. response = self.do_request(query)
  750. assert response.status_code == 400
  751. assert "sort by" in response.data["detail"]
  752. def test_latest_release_alias(self):
  753. project = self.create_project()
  754. event1 = self.store_event(
  755. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "release": "0.8"},
  756. project_id=project.id,
  757. )
  758. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  759. response = self.do_request(query)
  760. assert response.status_code == 200, response.content
  761. data = response.data["data"]
  762. assert data[0]["issue.id"] == event1.group_id
  763. assert data[0]["release"] == "0.8"
  764. event2 = self.store_event(
  765. data={"event_id": "a" * 32, "timestamp": self.min_ago, "release": "0.9"},
  766. project_id=project.id,
  767. )
  768. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  769. response = self.do_request(query)
  770. assert response.status_code == 200, response.content
  771. data = response.data["data"]
  772. assert data[0]["issue.id"] == event2.group_id
  773. assert data[0]["release"] == "0.9"
  774. def test_semver(self):
  775. release_1 = self.create_release(version="test@1.2.3")
  776. release_2 = self.create_release(version="test@1.2.4")
  777. release_3 = self.create_release(version="test@1.2.5")
  778. release_1_e_1 = self.store_event(
  779. data={"release": release_1.version, "timestamp": self.min_ago},
  780. project_id=self.project.id,
  781. ).event_id
  782. release_1_e_2 = self.store_event(
  783. data={"release": release_1.version, "timestamp": self.min_ago},
  784. project_id=self.project.id,
  785. ).event_id
  786. release_2_e_1 = self.store_event(
  787. data={"release": release_2.version, "timestamp": self.min_ago},
  788. project_id=self.project.id,
  789. ).event_id
  790. release_2_e_2 = self.store_event(
  791. data={"release": release_2.version, "timestamp": self.min_ago},
  792. project_id=self.project.id,
  793. ).event_id
  794. release_3_e_1 = self.store_event(
  795. data={"release": release_3.version, "timestamp": self.min_ago},
  796. project_id=self.project.id,
  797. ).event_id
  798. release_3_e_2 = self.store_event(
  799. data={"release": release_3.version, "timestamp": self.min_ago},
  800. project_id=self.project.id,
  801. ).event_id
  802. query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:>1.2.3"}
  803. response = self.do_request(query)
  804. assert response.status_code == 200, response.content
  805. assert {r["id"] for r in response.data["data"]} == {
  806. release_2_e_1,
  807. release_2_e_2,
  808. release_3_e_1,
  809. release_3_e_2,
  810. }
  811. query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:>=1.2.3"}
  812. response = self.do_request(query)
  813. assert response.status_code == 200, response.content
  814. assert {r["id"] for r in response.data["data"]} == {
  815. release_1_e_1,
  816. release_1_e_2,
  817. release_2_e_1,
  818. release_2_e_2,
  819. release_3_e_1,
  820. release_3_e_2,
  821. }
  822. query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:<1.2.4"}
  823. response = self.do_request(query)
  824. assert response.status_code == 200, response.content
  825. assert {r["id"] for r in response.data["data"]} == {
  826. release_1_e_1,
  827. release_1_e_2,
  828. }
  829. query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:1.2.3"}
  830. response = self.do_request(query)
  831. assert response.status_code == 200, response.content
  832. assert {r["id"] for r in response.data["data"]} == {
  833. release_1_e_1,
  834. release_1_e_2,
  835. }
  836. query = {"field": ["id"], "query": f"!{constants.SEMVER_ALIAS}:1.2.3"}
  837. response = self.do_request(query)
  838. assert response.status_code == 200, response.content
  839. assert {r["id"] for r in response.data["data"]} == {
  840. release_2_e_1,
  841. release_2_e_2,
  842. release_3_e_1,
  843. release_3_e_2,
  844. }
  845. def test_release_stage(self):
  846. replaced_release = self.create_release(
  847. version="replaced_release",
  848. environments=[self.environment],
  849. adopted=timezone.now(),
  850. unadopted=timezone.now(),
  851. )
  852. adopted_release = self.create_release(
  853. version="adopted_release",
  854. environments=[self.environment],
  855. adopted=timezone.now(),
  856. )
  857. self.create_release(version="not_adopted_release", environments=[self.environment])
  858. adopted_release_e_1 = self.store_event(
  859. data={
  860. "release": adopted_release.version,
  861. "timestamp": self.min_ago,
  862. "environment": self.environment.name,
  863. },
  864. project_id=self.project.id,
  865. ).event_id
  866. adopted_release_e_2 = self.store_event(
  867. data={
  868. "release": adopted_release.version,
  869. "timestamp": self.min_ago,
  870. "environment": self.environment.name,
  871. },
  872. project_id=self.project.id,
  873. ).event_id
  874. replaced_release_e_1 = self.store_event(
  875. data={
  876. "release": replaced_release.version,
  877. "timestamp": self.min_ago,
  878. "environment": self.environment.name,
  879. },
  880. project_id=self.project.id,
  881. ).event_id
  882. replaced_release_e_2 = self.store_event(
  883. data={
  884. "release": replaced_release.version,
  885. "timestamp": self.min_ago,
  886. "environment": self.environment.name,
  887. },
  888. project_id=self.project.id,
  889. ).event_id
  890. query = {
  891. "field": ["id"],
  892. "query": f"{constants.RELEASE_STAGE_ALIAS}:{ReleaseStages.ADOPTED}",
  893. "environment": [self.environment.name],
  894. }
  895. response = self.do_request(query)
  896. assert response.status_code == 200, response.content
  897. assert {r["id"] for r in response.data["data"]} == {
  898. adopted_release_e_1,
  899. adopted_release_e_2,
  900. }
  901. query = {
  902. "field": ["id"],
  903. "query": f"!{constants.RELEASE_STAGE_ALIAS}:{ReleaseStages.LOW_ADOPTION}",
  904. "environment": [self.environment.name],
  905. }
  906. response = self.do_request(query)
  907. assert response.status_code == 200, response.content
  908. assert {r["id"] for r in response.data["data"]} == {
  909. adopted_release_e_1,
  910. adopted_release_e_2,
  911. replaced_release_e_1,
  912. replaced_release_e_2,
  913. }
  914. query = {
  915. "field": ["id"],
  916. "query": f"{constants.RELEASE_STAGE_ALIAS}:[{ReleaseStages.ADOPTED}, {ReleaseStages.REPLACED}]",
  917. "environment": [self.environment.name],
  918. }
  919. response = self.do_request(query)
  920. assert response.status_code == 200, response.content
  921. assert {r["id"] for r in response.data["data"]} == {
  922. adopted_release_e_1,
  923. adopted_release_e_2,
  924. replaced_release_e_1,
  925. replaced_release_e_2,
  926. }
  927. def test_semver_package(self):
  928. release_1 = self.create_release(version="test@1.2.3")
  929. release_2 = self.create_release(version="test2@1.2.4")
  930. release_1_e_1 = self.store_event(
  931. data={"release": release_1.version, "timestamp": self.min_ago},
  932. project_id=self.project.id,
  933. ).event_id
  934. release_1_e_2 = self.store_event(
  935. data={"release": release_1.version, "timestamp": self.min_ago},
  936. project_id=self.project.id,
  937. ).event_id
  938. release_2_e_1 = self.store_event(
  939. data={"release": release_2.version, "timestamp": self.min_ago},
  940. project_id=self.project.id,
  941. ).event_id
  942. query = {"field": ["id"], "query": f"{constants.SEMVER_PACKAGE_ALIAS}:test"}
  943. response = self.do_request(query)
  944. assert response.status_code == 200, response.content
  945. assert {r["id"] for r in response.data["data"]} == {
  946. release_1_e_1,
  947. release_1_e_2,
  948. }
  949. query = {"field": ["id"], "query": f"{constants.SEMVER_PACKAGE_ALIAS}:test2"}
  950. response = self.do_request(query)
  951. assert response.status_code == 200, response.content
  952. assert {r["id"] for r in response.data["data"]} == {
  953. release_2_e_1,
  954. }
  955. def test_semver_build(self):
  956. release_1 = self.create_release(version="test@1.2.3+123")
  957. release_2 = self.create_release(version="test2@1.2.4+124")
  958. release_1_e_1 = self.store_event(
  959. data={"release": release_1.version, "timestamp": self.min_ago},
  960. project_id=self.project.id,
  961. ).event_id
  962. release_1_e_2 = self.store_event(
  963. data={"release": release_1.version, "timestamp": self.min_ago},
  964. project_id=self.project.id,
  965. ).event_id
  966. release_2_e_1 = self.store_event(
  967. data={"release": release_2.version, "timestamp": self.min_ago},
  968. project_id=self.project.id,
  969. ).event_id
  970. query = {"field": ["id"], "query": f"{constants.SEMVER_BUILD_ALIAS}:123"}
  971. response = self.do_request(query)
  972. assert response.status_code == 200, response.content
  973. assert {r["id"] for r in response.data["data"]} == {
  974. release_1_e_1,
  975. release_1_e_2,
  976. }
  977. query = {"field": ["id"], "query": f"{constants.SEMVER_BUILD_ALIAS}:124"}
  978. response = self.do_request(query)
  979. assert response.status_code == 200, response.content
  980. assert {r["id"] for r in response.data["data"]} == {
  981. release_2_e_1,
  982. }
  983. query = {"field": ["id"], "query": f"!{constants.SEMVER_BUILD_ALIAS}:124"}
  984. response = self.do_request(query)
  985. assert response.status_code == 200, response.content
  986. assert {r["id"] for r in response.data["data"]} == {
  987. release_1_e_1,
  988. release_1_e_2,
  989. }
  990. def test_aliased_fields(self):
  991. project = self.create_project()
  992. event1 = self.store_event(
  993. data={
  994. "event_id": "a" * 32,
  995. "timestamp": self.min_ago,
  996. "fingerprint": ["group_1"],
  997. "user": {"email": "foo@example.com"},
  998. },
  999. project_id=project.id,
  1000. )
  1001. event2 = self.store_event(
  1002. data={
  1003. "event_id": "b" * 32,
  1004. "timestamp": self.min_ago,
  1005. "fingerprint": ["group_2"],
  1006. "user": {"email": "foo@example.com"},
  1007. },
  1008. project_id=project.id,
  1009. )
  1010. self.store_event(
  1011. data={
  1012. "event_id": "c" * 32,
  1013. "timestamp": self.min_ago,
  1014. "fingerprint": ["group_2"],
  1015. "user": {"email": "bar@example.com"},
  1016. },
  1017. project_id=project.id,
  1018. )
  1019. query = {"field": ["issue.id", "count(id)", "count_unique(user)"], "orderby": "issue.id"}
  1020. response = self.do_request(query)
  1021. assert response.status_code == 200, response.content
  1022. assert len(response.data["data"]) == 2
  1023. data = response.data["data"]
  1024. assert data[0]["issue.id"] == event1.group_id
  1025. assert data[0]["count_id"] == 1
  1026. assert data[0]["count_unique_user"] == 1
  1027. assert "projectid" not in data[0]
  1028. assert "project.id" not in data[0]
  1029. assert data[1]["issue.id"] == event2.group_id
  1030. assert data[1]["count_id"] == 2
  1031. assert data[1]["count_unique_user"] == 2
  1032. def test_aggregate_field_with_dotted_param(self):
  1033. project = self.create_project()
  1034. event1 = self.store_event(
  1035. data={
  1036. "event_id": "a" * 32,
  1037. "timestamp": self.min_ago,
  1038. "fingerprint": ["group_1"],
  1039. "user": {"id": "123", "email": "foo@example.com"},
  1040. },
  1041. project_id=project.id,
  1042. )
  1043. event2 = self.store_event(
  1044. data={
  1045. "event_id": "b" * 32,
  1046. "timestamp": self.min_ago,
  1047. "fingerprint": ["group_2"],
  1048. "user": {"id": "123", "email": "foo@example.com"},
  1049. },
  1050. project_id=project.id,
  1051. )
  1052. self.store_event(
  1053. data={
  1054. "event_id": "c" * 32,
  1055. "timestamp": self.min_ago,
  1056. "fingerprint": ["group_2"],
  1057. "user": {"id": "456", "email": "bar@example.com"},
  1058. },
  1059. project_id=project.id,
  1060. )
  1061. query = {
  1062. "field": ["issue.id", "issue_title", "count(id)", "count_unique(user.email)"],
  1063. "orderby": "issue.id",
  1064. }
  1065. response = self.do_request(query)
  1066. assert response.status_code == 200, response.content
  1067. assert len(response.data["data"]) == 2
  1068. data = response.data["data"]
  1069. assert data[0]["issue.id"] == event1.group_id
  1070. assert data[0]["count_id"] == 1
  1071. assert data[0]["count_unique_user_email"] == 1
  1072. assert "projectid" not in data[0]
  1073. assert "project.id" not in data[0]
  1074. assert data[1]["issue.id"] == event2.group_id
  1075. assert data[1]["count_id"] == 2
  1076. assert data[1]["count_unique_user_email"] == 2
  1077. def test_failure_rate_alias_field(self):
  1078. project = self.create_project()
  1079. data = load_data("transaction", timestamp=before_now(minutes=1))
  1080. data["transaction"] = "/failure_rate/success"
  1081. self.store_event(data, project_id=project.id)
  1082. data = load_data("transaction", timestamp=before_now(minutes=1))
  1083. data["transaction"] = "/failure_rate/unknown"
  1084. data["contexts"]["trace"]["status"] = "unknown_error"
  1085. self.store_event(data, project_id=project.id)
  1086. for i in range(6):
  1087. data = load_data("transaction", timestamp=before_now(minutes=1))
  1088. data["transaction"] = f"/failure_rate/{i}"
  1089. data["contexts"]["trace"]["status"] = "unauthenticated"
  1090. self.store_event(data, project_id=project.id)
  1091. query = {"field": ["failure_rate()"], "query": "event.type:transaction"}
  1092. response = self.do_request(query)
  1093. assert response.status_code == 200, response.content
  1094. assert len(response.data["data"]) == 1
  1095. data = response.data["data"]
  1096. assert data[0]["failure_rate"] == 0.75
  1097. def test_count_miserable_alias_field(self):
  1098. project = self.create_project()
  1099. events = [
  1100. ("one", 300),
  1101. ("one", 300),
  1102. ("two", 3000),
  1103. ("two", 3000),
  1104. ("three", 300),
  1105. ("three", 3000),
  1106. ]
  1107. for idx, event in enumerate(events):
  1108. data = load_data(
  1109. "transaction",
  1110. timestamp=before_now(minutes=(1 + idx)),
  1111. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1112. )
  1113. data["event_id"] = f"{idx}" * 32
  1114. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  1115. data["user"] = {"email": f"{event[0]}@example.com"}
  1116. self.store_event(data, project_id=project.id)
  1117. query = {"field": ["count_miserable(user, 300)"], "query": "event.type:transaction"}
  1118. response = self.do_request(query)
  1119. assert response.status_code == 200, response.content
  1120. assert len(response.data["data"]) == 1
  1121. data = response.data["data"]
  1122. assert data[0]["count_miserable_user_300"] == 2
  1123. @mock.patch(
  1124. "sentry.search.events.fields.MAX_QUERYABLE_TRANSACTION_THRESHOLDS",
  1125. MAX_QUERYABLE_TRANSACTION_THRESHOLDS,
  1126. )
  1127. @mock.patch(
  1128. "sentry.search.events.datasets.discover.MAX_QUERYABLE_TRANSACTION_THRESHOLDS",
  1129. MAX_QUERYABLE_TRANSACTION_THRESHOLDS,
  1130. )
  1131. def test_too_many_transaction_thresholds(self):
  1132. project_transaction_thresholds = []
  1133. project_ids = []
  1134. for i in range(MAX_QUERYABLE_TRANSACTION_THRESHOLDS + 1):
  1135. project = self.create_project(name=f"bulk_txn_{i}")
  1136. project_ids.append(project.id)
  1137. project_transaction_thresholds.append(
  1138. ProjectTransactionThreshold(
  1139. organization=self.organization,
  1140. project=project,
  1141. threshold=400,
  1142. metric=TransactionMetric.LCP.value,
  1143. )
  1144. )
  1145. ProjectTransactionThreshold.objects.bulk_create(project_transaction_thresholds)
  1146. events = [
  1147. ("one", 400),
  1148. ("one", 400),
  1149. ("two", 3000),
  1150. ("two", 3000),
  1151. ("three", 300),
  1152. ("three", 3000),
  1153. ]
  1154. for idx, event in enumerate(events):
  1155. data = load_data(
  1156. "transaction",
  1157. timestamp=before_now(minutes=(1 + idx)),
  1158. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1159. )
  1160. data["event_id"] = f"{idx}" * 32
  1161. data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
  1162. data["user"] = {"email": f"{idx}@example.com"}
  1163. self.store_event(data, project_id=project_ids[0])
  1164. query = {
  1165. "field": [
  1166. "transaction",
  1167. "count_miserable(user)",
  1168. ],
  1169. "query": "event.type:transaction",
  1170. "project": project_ids,
  1171. }
  1172. response = self.do_request(
  1173. query,
  1174. features={
  1175. "organizations:discover-basic": True,
  1176. "organizations:global-views": True,
  1177. },
  1178. )
  1179. assert response.status_code == 400
  1180. assert (
  1181. response.data["detail"]
  1182. == "Exceeded 1 configured transaction thresholds limit, try with fewer Projects."
  1183. )
  1184. def test_count_miserable_new_alias_field(self):
  1185. project = self.create_project()
  1186. ProjectTransactionThreshold.objects.create(
  1187. project=project,
  1188. organization=project.organization,
  1189. threshold=400,
  1190. metric=TransactionMetric.DURATION.value,
  1191. )
  1192. events = [
  1193. ("one", 400),
  1194. ("one", 400),
  1195. ("two", 3000),
  1196. ("two", 3000),
  1197. ("three", 300),
  1198. ("three", 3000),
  1199. ]
  1200. for idx, event in enumerate(events):
  1201. data = load_data(
  1202. "transaction",
  1203. timestamp=before_now(minutes=(1 + idx)),
  1204. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1205. )
  1206. data["event_id"] = f"{idx}" * 32
  1207. data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
  1208. data["user"] = {"email": f"{idx}@example.com"}
  1209. self.store_event(data, project_id=project.id)
  1210. query = {
  1211. "field": [
  1212. "transaction",
  1213. "count_miserable(user)",
  1214. ],
  1215. "query": "event.type:transaction",
  1216. "project": [project.id],
  1217. "sort": "count_miserable_user",
  1218. }
  1219. response = self.do_request(
  1220. query,
  1221. )
  1222. assert response.status_code == 200, response.content
  1223. assert len(response.data["data"]) == 3
  1224. data = response.data["data"]
  1225. assert data[0]["count_miserable_user"] == 0
  1226. assert data[1]["count_miserable_user"] == 1
  1227. assert data[2]["count_miserable_user"] == 2
  1228. query["query"] = "event.type:transaction count_miserable(user):>0"
  1229. response = self.do_request(
  1230. query,
  1231. )
  1232. assert response.status_code == 200, response.content
  1233. assert len(response.data["data"]) == 2
  1234. data = response.data["data"]
  1235. assert abs(data[0]["count_miserable_user"]) == 1
  1236. assert abs(data[1]["count_miserable_user"]) == 2
  1237. def test_user_misery_alias_field(self):
  1238. project = self.create_project()
  1239. events = [
  1240. ("one", 300),
  1241. ("one", 300),
  1242. ("two", 3000),
  1243. ("two", 3000),
  1244. ("three", 300),
  1245. ("three", 3000),
  1246. ]
  1247. for idx, event in enumerate(events):
  1248. data = load_data(
  1249. "transaction",
  1250. timestamp=before_now(minutes=(1 + idx)),
  1251. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1252. )
  1253. data["event_id"] = f"{idx}" * 32
  1254. data["transaction"] = f"/user_misery/{idx}"
  1255. data["user"] = {"email": f"{event[0]}@example.com"}
  1256. self.store_event(data, project_id=project.id)
  1257. query = {"field": ["user_misery(300)"], "query": "event.type:transaction"}
  1258. response = self.do_request(query)
  1259. assert response.status_code == 200, response.content
  1260. assert len(response.data["data"]) == 1
  1261. data = response.data["data"]
  1262. assert abs(data[0]["user_misery_300"] - 0.0653) < 0.0001
  1263. def test_apdex_new_alias_field(self):
  1264. project = self.create_project()
  1265. ProjectTransactionThreshold.objects.create(
  1266. project=project,
  1267. organization=project.organization,
  1268. threshold=400,
  1269. metric=TransactionMetric.DURATION.value,
  1270. )
  1271. events = [
  1272. ("one", 400),
  1273. ("one", 400),
  1274. ("two", 3000),
  1275. ("two", 3000),
  1276. ("three", 300),
  1277. ("three", 3000),
  1278. ]
  1279. for idx, event in enumerate(events):
  1280. data = load_data(
  1281. "transaction",
  1282. timestamp=before_now(minutes=(1 + idx)),
  1283. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1284. )
  1285. data["event_id"] = f"{idx}" * 32
  1286. data["transaction"] = f"/apdex/new/{event[0]}"
  1287. data["user"] = {"email": f"{idx}@example.com"}
  1288. self.store_event(data, project_id=project.id)
  1289. query = {
  1290. "field": [
  1291. "transaction",
  1292. "apdex()",
  1293. ],
  1294. "query": "event.type:transaction",
  1295. "project": [project.id],
  1296. "sort": "-apdex",
  1297. }
  1298. response = self.do_request(
  1299. query,
  1300. )
  1301. assert response.status_code == 200, response.content
  1302. assert len(response.data["data"]) == 3
  1303. data = response.data["data"]
  1304. assert data[0]["apdex"] == 1.0
  1305. assert data[1]["apdex"] == 0.5
  1306. assert data[2]["apdex"] == 0.0
  1307. query["query"] = "event.type:transaction apdex():>0.50"
  1308. response = self.do_request(
  1309. query,
  1310. )
  1311. assert response.status_code == 200, response.content
  1312. assert len(response.data["data"]) == 1
  1313. data = response.data["data"]
  1314. assert data[0]["apdex"] == 1.0
  1315. def test_user_misery_alias_field_with_project_threshold(self):
  1316. project = self.create_project()
  1317. ProjectTransactionThreshold.objects.create(
  1318. project=project,
  1319. organization=project.organization,
  1320. threshold=400,
  1321. metric=TransactionMetric.DURATION.value,
  1322. )
  1323. events = [
  1324. ("one", 400),
  1325. ("one", 400),
  1326. ("two", 3000),
  1327. ("two", 3000),
  1328. ("three", 300),
  1329. ("three", 3000),
  1330. ]
  1331. for idx, event in enumerate(events):
  1332. data = load_data(
  1333. "transaction",
  1334. timestamp=before_now(minutes=(1 + idx)),
  1335. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1336. )
  1337. data["event_id"] = f"{idx}" * 32
  1338. data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
  1339. data["user"] = {"email": f"{idx}@example.com"}
  1340. self.store_event(data, project_id=project.id)
  1341. query = {
  1342. "field": [
  1343. "transaction",
  1344. "user_misery()",
  1345. ],
  1346. "orderby": "user_misery()",
  1347. "query": "event.type:transaction",
  1348. "project": [project.id],
  1349. }
  1350. response = self.do_request(query)
  1351. assert response.status_code == 200, response.content
  1352. assert len(response.data["data"]) == 3
  1353. data = response.data["data"]
  1354. assert abs(data[0]["user_misery"] - 0.04916) < 0.0001
  1355. assert abs(data[1]["user_misery"] - 0.05751) < 0.0001
  1356. assert abs(data[2]["user_misery"] - 0.06586) < 0.0001
  1357. query["query"] = "event.type:transaction user_misery():>0.050"
  1358. response = self.do_request(
  1359. query,
  1360. )
  1361. assert response.status_code == 200, response.content
  1362. assert len(response.data["data"]) == 2
  1363. data = response.data["data"]
  1364. assert abs(data[0]["user_misery"] - 0.05751) < 0.0001
  1365. assert abs(data[1]["user_misery"] - 0.06586) < 0.0001
  1366. def test_user_misery_alias_field_with_transaction_threshold(self):
  1367. project = self.create_project()
  1368. events = [
  1369. ("one", 300),
  1370. ("two", 300),
  1371. ("one", 3000),
  1372. ("two", 3000),
  1373. ("three", 400),
  1374. ("four", 4000),
  1375. ]
  1376. for idx, event in enumerate(events):
  1377. data = load_data(
  1378. "transaction",
  1379. timestamp=before_now(minutes=(1 + idx)),
  1380. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1381. )
  1382. data["event_id"] = f"{idx}" * 32
  1383. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  1384. data["user"] = {"email": f"{event[0]}@example.com"}
  1385. self.store_event(data, project_id=project.id)
  1386. if idx % 2:
  1387. ProjectTransactionThresholdOverride.objects.create(
  1388. transaction=f"/count_miserable/horribilis/{idx}",
  1389. project=project,
  1390. organization=project.organization,
  1391. threshold=100 * idx,
  1392. metric=TransactionMetric.DURATION.value,
  1393. )
  1394. query = {
  1395. "field": [
  1396. "transaction",
  1397. "user_misery()",
  1398. ],
  1399. "query": "event.type:transaction",
  1400. "orderby": "transaction",
  1401. "project": [project.id],
  1402. }
  1403. response = self.do_request(
  1404. query,
  1405. )
  1406. assert response.status_code == 200, response.content
  1407. expected = [
  1408. ("/count_miserable/horribilis/0", ["duration", 300], 0.049578),
  1409. ("/count_miserable/horribilis/1", ["duration", 100], 0.049578),
  1410. ("/count_miserable/horribilis/2", ["duration", 300], 0.058),
  1411. ("/count_miserable/horribilis/3", ["duration", 300], 0.058),
  1412. ("/count_miserable/horribilis/4", ["duration", 300], 0.049578),
  1413. ("/count_miserable/horribilis/5", ["duration", 500], 0.058),
  1414. ]
  1415. assert len(response.data["data"]) == 6
  1416. data = response.data["data"]
  1417. for i, record in enumerate(expected):
  1418. name, threshold_config, misery = record
  1419. assert data[i]["transaction"] == name
  1420. assert data[i]["project_threshold_config"] == threshold_config
  1421. assert abs(data[i]["user_misery"] - misery) < 0.0001
  1422. query["query"] = "event.type:transaction user_misery():>0.050"
  1423. response = self.do_request(
  1424. query,
  1425. )
  1426. assert response.status_code == 200, response.content
  1427. assert len(response.data["data"]) == 3
  1428. data = response.data["data"]
  1429. assert abs(data[0]["user_misery"] - 0.058) < 0.0001
  1430. assert abs(data[1]["user_misery"] - 0.058) < 0.0001
  1431. assert abs(data[2]["user_misery"] - 0.058) < 0.0001
  1432. def test_user_misery_alias_field_with_transaction_threshold_and_project_threshold(self):
  1433. project = self.create_project()
  1434. ProjectTransactionThreshold.objects.create(
  1435. project=project,
  1436. organization=project.organization,
  1437. threshold=100,
  1438. metric=TransactionMetric.DURATION.value,
  1439. )
  1440. events = [
  1441. ("one", 300),
  1442. ("two", 300),
  1443. ("one", 3000),
  1444. ("two", 3000),
  1445. ("three", 400),
  1446. ("four", 4000),
  1447. ]
  1448. for idx, event in enumerate(events):
  1449. data = load_data(
  1450. "transaction",
  1451. timestamp=before_now(minutes=(1 + idx)),
  1452. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1453. )
  1454. data["event_id"] = f"{idx}" * 32
  1455. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  1456. data["user"] = {"email": f"{event[0]}@example.com"}
  1457. self.store_event(data, project_id=project.id)
  1458. if idx % 2:
  1459. ProjectTransactionThresholdOverride.objects.create(
  1460. transaction=f"/count_miserable/horribilis/{idx}",
  1461. project=project,
  1462. organization=project.organization,
  1463. threshold=100 * idx,
  1464. metric=TransactionMetric.DURATION.value,
  1465. )
  1466. project2 = self.create_project()
  1467. data = load_data("transaction", timestamp=before_now(minutes=1))
  1468. data["transaction"] = "/count_miserable/horribilis/project2"
  1469. data["user"] = {"email": "project2@example.com"}
  1470. self.store_event(data, project_id=project2.id)
  1471. query = {
  1472. "field": [
  1473. "transaction",
  1474. "user_misery()",
  1475. ],
  1476. "query": "event.type:transaction",
  1477. "orderby": "transaction",
  1478. "project": [project.id, project2.id],
  1479. }
  1480. response = self.do_request(
  1481. query,
  1482. features={
  1483. "organizations:discover-basic": True,
  1484. "organizations:global-views": True,
  1485. },
  1486. )
  1487. assert response.status_code == 200, response.content
  1488. expected = [
  1489. (
  1490. "/count_miserable/horribilis/0",
  1491. ["duration", 100],
  1492. 0.049578,
  1493. ), # Uses project threshold
  1494. ("/count_miserable/horribilis/1", ["duration", 100], 0.049578), # Uses txn threshold
  1495. ("/count_miserable/horribilis/2", ["duration", 100], 0.058), # Uses project threshold
  1496. ("/count_miserable/horribilis/3", ["duration", 300], 0.058), # Uses txn threshold
  1497. (
  1498. "/count_miserable/horribilis/4",
  1499. ["duration", 100],
  1500. 0.049578,
  1501. ), # Uses project threshold
  1502. ("/count_miserable/horribilis/5", ["duration", 500], 0.058), # Uses txn threshold
  1503. ("/count_miserable/horribilis/project2", ["duration", 300], 0.058), # Uses fallback
  1504. ]
  1505. assert len(response.data["data"]) == 7
  1506. data = response.data["data"]
  1507. for i, record in enumerate(expected):
  1508. name, threshold_config, misery = record
  1509. assert data[i]["transaction"] == name
  1510. assert data[i]["project_threshold_config"] == threshold_config
  1511. assert abs(data[i]["user_misery"] - misery) < 0.0001
  1512. query["query"] = "event.type:transaction user_misery():>0.050"
  1513. response = self.do_request(
  1514. query,
  1515. features={
  1516. "organizations:discover-basic": True,
  1517. "organizations:global-views": True,
  1518. },
  1519. )
  1520. assert response.status_code == 200, response.content
  1521. assert len(response.data["data"]) == 4
  1522. def test_aggregation(self):
  1523. project = self.create_project()
  1524. self.store_event(
  1525. data={
  1526. "event_id": "a" * 32,
  1527. "timestamp": self.min_ago,
  1528. "fingerprint": ["group_1"],
  1529. "user": {"email": "foo@example.com"},
  1530. "environment": "prod",
  1531. "tags": {"sub_customer.is-Enterprise-42": "1"},
  1532. },
  1533. project_id=project.id,
  1534. )
  1535. self.store_event(
  1536. data={
  1537. "event_id": "b" * 32,
  1538. "timestamp": self.min_ago,
  1539. "fingerprint": ["group_2"],
  1540. "user": {"email": "foo@example.com"},
  1541. "environment": "staging",
  1542. "tags": {"sub_customer.is-Enterprise-42": "1"},
  1543. },
  1544. project_id=project.id,
  1545. )
  1546. self.store_event(
  1547. data={
  1548. "event_id": "c" * 32,
  1549. "timestamp": self.min_ago,
  1550. "fingerprint": ["group_2"],
  1551. "user": {"email": "foo@example.com"},
  1552. "environment": "prod",
  1553. "tags": {"sub_customer.is-Enterprise-42": "0"},
  1554. },
  1555. project_id=project.id,
  1556. )
  1557. self.store_event(
  1558. data={
  1559. "event_id": "d" * 32,
  1560. "timestamp": self.min_ago,
  1561. "fingerprint": ["group_2"],
  1562. "user": {"email": "foo@example.com"},
  1563. "environment": "prod",
  1564. "tags": {"sub_customer.is-Enterprise-42": "1"},
  1565. },
  1566. project_id=project.id,
  1567. )
  1568. query = {
  1569. "field": ["sub_customer.is-Enterprise-42", "count(sub_customer.is-Enterprise-42)"],
  1570. "orderby": "sub_customer.is-Enterprise-42",
  1571. }
  1572. response = self.do_request(query)
  1573. assert response.status_code == 200, response.content
  1574. assert len(response.data["data"]) == 2
  1575. data = response.data["data"]
  1576. assert data[0]["count_sub_customer_is_Enterprise_42"] == 1
  1577. assert data[1]["count_sub_customer_is_Enterprise_42"] == 3
  1578. def test_aggregation_comparison(self):
  1579. project = self.create_project()
  1580. self.store_event(
  1581. data={
  1582. "event_id": "a" * 32,
  1583. "timestamp": self.min_ago,
  1584. "fingerprint": ["group_1"],
  1585. "user": {"email": "foo@example.com"},
  1586. },
  1587. project_id=project.id,
  1588. )
  1589. event = self.store_event(
  1590. data={
  1591. "event_id": "b" * 32,
  1592. "timestamp": self.min_ago,
  1593. "fingerprint": ["group_2"],
  1594. "user": {"email": "foo@example.com"},
  1595. },
  1596. project_id=project.id,
  1597. )
  1598. self.store_event(
  1599. data={
  1600. "event_id": "c" * 32,
  1601. "timestamp": self.min_ago,
  1602. "fingerprint": ["group_2"],
  1603. "user": {"email": "bar@example.com"},
  1604. },
  1605. project_id=project.id,
  1606. )
  1607. self.store_event(
  1608. data={
  1609. "event_id": "d" * 32,
  1610. "timestamp": self.min_ago,
  1611. "fingerprint": ["group_3"],
  1612. "user": {"email": "bar@example.com"},
  1613. },
  1614. project_id=project.id,
  1615. )
  1616. self.store_event(
  1617. data={
  1618. "event_id": "e" * 32,
  1619. "timestamp": self.min_ago,
  1620. "fingerprint": ["group_3"],
  1621. "user": {"email": "bar@example.com"},
  1622. },
  1623. project_id=project.id,
  1624. )
  1625. query = {
  1626. "field": ["issue.id", "count(id)", "count_unique(user)"],
  1627. "query": "count(id):>1 count_unique(user):>1",
  1628. "orderby": "issue.id",
  1629. }
  1630. response = self.do_request(query)
  1631. assert response.status_code == 200, response.content
  1632. assert len(response.data["data"]) == 1
  1633. data = response.data["data"]
  1634. assert data[0]["issue.id"] == event.group_id
  1635. assert data[0]["count_id"] == 2
  1636. assert data[0]["count_unique_user"] == 2
  1637. def test_aggregation_alias_comparison(self):
  1638. project = self.create_project()
  1639. data = load_data(
  1640. "transaction",
  1641. timestamp=before_now(minutes=1),
  1642. start_timestamp=before_now(minutes=1, seconds=5),
  1643. )
  1644. data["transaction"] = "/aggregates/1"
  1645. self.store_event(data, project_id=project.id)
  1646. data = load_data(
  1647. "transaction",
  1648. timestamp=before_now(minutes=1),
  1649. start_timestamp=before_now(minutes=1, seconds=3),
  1650. )
  1651. data["transaction"] = "/aggregates/2"
  1652. event = self.store_event(data, project_id=project.id)
  1653. query = {
  1654. "field": ["transaction", "p95()"],
  1655. "query": "event.type:transaction p95():<4000",
  1656. "orderby": ["transaction"],
  1657. }
  1658. response = self.do_request(query)
  1659. assert response.status_code == 200, response.content
  1660. assert len(response.data["data"]) == 1
  1661. data = response.data["data"]
  1662. assert data[0]["transaction"] == event.transaction
  1663. assert data[0]["p95"] == 3000
  1664. def test_auto_aggregations(self):
  1665. project = self.create_project()
  1666. data = load_data(
  1667. "transaction",
  1668. timestamp=before_now(minutes=1),
  1669. start_timestamp=before_now(minutes=1, seconds=5),
  1670. )
  1671. data["transaction"] = "/aggregates/1"
  1672. self.store_event(data, project_id=project.id)
  1673. data = load_data(
  1674. "transaction",
  1675. timestamp=before_now(minutes=1),
  1676. start_timestamp=before_now(minutes=1, seconds=3),
  1677. )
  1678. data["transaction"] = "/aggregates/2"
  1679. event = self.store_event(data, project_id=project.id)
  1680. query = {
  1681. "field": ["transaction", "p75()"],
  1682. "query": "event.type:transaction p95():<4000",
  1683. "orderby": ["transaction"],
  1684. }
  1685. response = self.do_request(query)
  1686. assert response.status_code == 200, response.content
  1687. assert len(response.data["data"]) == 1
  1688. data = response.data["data"]
  1689. assert data[0]["transaction"] == event.transaction
  1690. query = {
  1691. "field": ["transaction"],
  1692. "query": "event.type:transaction p95():<4000",
  1693. "orderby": ["transaction"],
  1694. }
  1695. response = self.do_request(query)
  1696. assert response.status_code == 400, response.content
  1697. def test_aggregation_comparison_with_conditions(self):
  1698. project = self.create_project()
  1699. self.store_event(
  1700. data={
  1701. "event_id": "a" * 32,
  1702. "timestamp": self.min_ago,
  1703. "fingerprint": ["group_1"],
  1704. "user": {"email": "foo@example.com"},
  1705. "environment": "prod",
  1706. },
  1707. project_id=project.id,
  1708. )
  1709. self.store_event(
  1710. data={
  1711. "event_id": "b" * 32,
  1712. "timestamp": self.min_ago,
  1713. "fingerprint": ["group_2"],
  1714. "user": {"email": "foo@example.com"},
  1715. "environment": "staging",
  1716. },
  1717. project_id=project.id,
  1718. )
  1719. event = self.store_event(
  1720. data={
  1721. "event_id": "c" * 32,
  1722. "timestamp": self.min_ago,
  1723. "fingerprint": ["group_2"],
  1724. "user": {"email": "foo@example.com"},
  1725. "environment": "prod",
  1726. },
  1727. project_id=project.id,
  1728. )
  1729. self.store_event(
  1730. data={
  1731. "event_id": "d" * 32,
  1732. "timestamp": self.min_ago,
  1733. "fingerprint": ["group_2"],
  1734. "user": {"email": "foo@example.com"},
  1735. "environment": "prod",
  1736. },
  1737. project_id=project.id,
  1738. )
  1739. query = {
  1740. "field": ["issue.id", "count(id)"],
  1741. "query": "count(id):>1 user.email:foo@example.com environment:prod",
  1742. "orderby": "issue.id",
  1743. }
  1744. response = self.do_request(query)
  1745. assert response.status_code == 200, response.content
  1746. assert len(response.data["data"]) == 1
  1747. data = response.data["data"]
  1748. assert data[0]["issue.id"] == event.group_id
  1749. assert data[0]["count_id"] == 2
  1750. def test_aggregation_date_comparison_with_conditions(self):
  1751. project = self.create_project()
  1752. event = self.store_event(
  1753. data={
  1754. "event_id": "a" * 32,
  1755. "timestamp": self.min_ago,
  1756. "fingerprint": ["group_1"],
  1757. "user": {"email": "foo@example.com"},
  1758. "environment": "prod",
  1759. },
  1760. project_id=project.id,
  1761. )
  1762. self.store_event(
  1763. data={
  1764. "event_id": "b" * 32,
  1765. "timestamp": self.min_ago,
  1766. "fingerprint": ["group_2"],
  1767. "user": {"email": "foo@example.com"},
  1768. "environment": "staging",
  1769. },
  1770. project_id=project.id,
  1771. )
  1772. self.store_event(
  1773. data={
  1774. "event_id": "c" * 32,
  1775. "timestamp": self.min_ago,
  1776. "fingerprint": ["group_2"],
  1777. "user": {"email": "foo@example.com"},
  1778. "environment": "prod",
  1779. },
  1780. project_id=project.id,
  1781. )
  1782. self.store_event(
  1783. data={
  1784. "event_id": "d" * 32,
  1785. "timestamp": self.min_ago,
  1786. "fingerprint": ["group_2"],
  1787. "user": {"email": "foo@example.com"},
  1788. "environment": "prod",
  1789. },
  1790. project_id=project.id,
  1791. )
  1792. query = {
  1793. "field": ["issue.id", "max(timestamp)"],
  1794. "query": "max(timestamp):>1 user.email:foo@example.com environment:prod",
  1795. "orderby": "issue.id",
  1796. }
  1797. response = self.do_request(query)
  1798. assert response.status_code == 200, response.content
  1799. assert len(response.data["data"]) == 2
  1800. assert response.data["meta"]["max_timestamp"] == "date"
  1801. data = response.data["data"]
  1802. assert data[0]["issue.id"] == event.group_id
  1803. def test_percentile_function(self):
  1804. project = self.create_project()
  1805. data = load_data(
  1806. "transaction",
  1807. timestamp=before_now(minutes=1),
  1808. start_timestamp=before_now(minutes=1, seconds=5),
  1809. )
  1810. data["transaction"] = "/aggregates/1"
  1811. event1 = self.store_event(data, project_id=project.id)
  1812. data = load_data(
  1813. "transaction",
  1814. timestamp=before_now(minutes=1),
  1815. start_timestamp=before_now(minutes=1, seconds=3),
  1816. )
  1817. data["transaction"] = "/aggregates/2"
  1818. event2 = self.store_event(data, project_id=project.id)
  1819. query = {
  1820. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  1821. "query": "event.type:transaction",
  1822. "orderby": ["transaction"],
  1823. }
  1824. response = self.do_request(query)
  1825. assert response.status_code == 200, response.content
  1826. assert len(response.data["data"]) == 2
  1827. data = response.data["data"]
  1828. assert data[0]["transaction"] == event1.transaction
  1829. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  1830. assert data[1]["transaction"] == event2.transaction
  1831. assert data[1]["percentile_transaction_duration_0_95"] == 3000
  1832. def test_percentile_function_as_condition(self):
  1833. project = self.create_project()
  1834. data = load_data(
  1835. "transaction",
  1836. timestamp=before_now(minutes=1),
  1837. start_timestamp=before_now(minutes=1, seconds=5),
  1838. )
  1839. data["transaction"] = "/aggregates/1"
  1840. event1 = self.store_event(data, project_id=project.id)
  1841. data = load_data(
  1842. "transaction",
  1843. timestamp=before_now(minutes=1),
  1844. start_timestamp=before_now(minutes=1, seconds=3),
  1845. )
  1846. data["transaction"] = "/aggregates/2"
  1847. self.store_event(data, project_id=project.id)
  1848. query = {
  1849. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  1850. "query": "event.type:transaction percentile(transaction.duration, 0.95):>4000",
  1851. "orderby": ["transaction"],
  1852. }
  1853. response = self.do_request(query)
  1854. assert response.status_code == 200, response.content
  1855. assert len(response.data["data"]) == 1
  1856. data = response.data["data"]
  1857. assert data[0]["transaction"] == event1.transaction
  1858. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  1859. def test_epm_function(self):
  1860. project = self.create_project()
  1861. data = load_data(
  1862. "transaction",
  1863. timestamp=before_now(minutes=1),
  1864. start_timestamp=before_now(minutes=1, seconds=5),
  1865. )
  1866. data["transaction"] = "/aggregates/1"
  1867. event1 = self.store_event(data, project_id=project.id)
  1868. data = load_data(
  1869. "transaction",
  1870. timestamp=before_now(minutes=1),
  1871. start_timestamp=before_now(minutes=1, seconds=3),
  1872. )
  1873. data["transaction"] = "/aggregates/2"
  1874. event2 = self.store_event(data, project_id=project.id)
  1875. query = {
  1876. "field": ["transaction", "epm()"],
  1877. "query": "event.type:transaction",
  1878. "orderby": ["transaction"],
  1879. "statsPeriod": "2m",
  1880. }
  1881. response = self.do_request(query)
  1882. assert response.status_code == 200, response.content
  1883. assert len(response.data["data"]) == 2
  1884. data = response.data["data"]
  1885. assert data[0]["transaction"] == event1.transaction
  1886. assert data[0]["epm"] == 0.5
  1887. assert data[1]["transaction"] == event2.transaction
  1888. assert data[1]["epm"] == 0.5
  1889. def test_nonexistent_fields(self):
  1890. project = self.create_project()
  1891. self.store_event(
  1892. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1893. project_id=project.id,
  1894. )
  1895. query = {"field": ["issue_world.id"]}
  1896. response = self.do_request(query)
  1897. assert response.status_code == 200, response.content
  1898. assert response.data["data"][0]["issue_world.id"] == ""
  1899. def test_no_requested_fields_or_grouping(self):
  1900. project = self.create_project()
  1901. self.store_event(
  1902. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1903. project_id=project.id,
  1904. )
  1905. query = {"query": "test"}
  1906. response = self.do_request(query)
  1907. assert response.status_code == 400, response.content
  1908. assert response.data["detail"] == "No columns selected"
  1909. def test_condition_on_aggregate_misses(self):
  1910. project = self.create_project()
  1911. self.store_event(
  1912. data={
  1913. "event_id": "c" * 32,
  1914. "timestamp": self.min_ago,
  1915. "fingerprint": ["group_2"],
  1916. "user": {"email": "bar@example.com"},
  1917. },
  1918. project_id=project.id,
  1919. )
  1920. query = {"field": ["issue.id"], "query": "event_count:>0", "orderby": "issue.id"}
  1921. response = self.do_request(query)
  1922. assert response.status_code == 200, response.content
  1923. assert len(response.data["data"]) == 0
  1924. def test_next_prev_link_headers(self):
  1925. project = self.create_project()
  1926. events = [("a", "group_1"), ("b", "group_2"), ("c", "group_2"), ("d", "group_2")]
  1927. for e in events:
  1928. self.store_event(
  1929. data={
  1930. "event_id": e[0] * 32,
  1931. "timestamp": self.min_ago,
  1932. "fingerprint": [e[1]],
  1933. "user": {"email": "foo@example.com"},
  1934. "tags": {"language": "C++"},
  1935. },
  1936. project_id=project.id,
  1937. )
  1938. query = {
  1939. "field": ["count(id)", "issue.id", "context.key"],
  1940. "sort": "-count_id",
  1941. "query": "language:C++",
  1942. }
  1943. response = self.do_request(query)
  1944. assert response.status_code == 200, response.content
  1945. links = parse_link_header(response["Link"])
  1946. for link in links:
  1947. assert "field=issue.id" in link
  1948. assert "field=count%28id%29" in link
  1949. assert "field=context.key" in link
  1950. assert "sort=-count_id" in link
  1951. assert "query=language%3AC%2B%2B" in link
  1952. assert len(response.data["data"]) == 2
  1953. data = response.data["data"]
  1954. assert data[0]["count_id"] == 3
  1955. assert data[1]["count_id"] == 1
  1956. def test_empty_count_query(self):
  1957. project = self.create_project()
  1958. event = self.store_event(
  1959. data={
  1960. "event_id": "a" * 32,
  1961. "timestamp": iso_format(before_now(minutes=5)),
  1962. "fingerprint": ["1123581321"],
  1963. "user": {"email": "foo@example.com"},
  1964. "tags": {"language": "C++"},
  1965. },
  1966. project_id=project.id,
  1967. )
  1968. query = {
  1969. "field": ["count()"],
  1970. "query": "issue.id:%d timestamp:>%s" % (event.group_id, self.min_ago),
  1971. "statsPeriod": "14d",
  1972. }
  1973. response = self.do_request(query)
  1974. assert response.status_code == 200, response.content
  1975. data = response.data["data"]
  1976. assert len(data) == 1
  1977. assert data[0]["count"] == 0
  1978. def test_stack_wildcard_condition(self):
  1979. project = self.create_project()
  1980. data = load_data("javascript")
  1981. data["timestamp"] = self.min_ago
  1982. self.store_event(data=data, project_id=project.id)
  1983. query = {"field": ["stack.filename", "message"], "query": "stack.filename:*.js"}
  1984. response = self.do_request(query)
  1985. assert response.status_code == 200, response.content
  1986. assert len(response.data["data"]) == 1
  1987. assert response.data["meta"]["message"] == "string"
  1988. def test_email_wildcard_condition(self):
  1989. project = self.create_project()
  1990. data = load_data("javascript")
  1991. data["timestamp"] = self.min_ago
  1992. self.store_event(data=data, project_id=project.id)
  1993. query = {"field": ["stack.filename", "message"], "query": "user.email:*@example.org"}
  1994. response = self.do_request(query)
  1995. assert response.status_code == 200, response.content
  1996. assert len(response.data["data"]) == 1
  1997. assert response.data["meta"]["message"] == "string"
  1998. def test_release_wildcard_condition(self):
  1999. release = self.create_release(version="test@1.2.3+123")
  2000. self.store_event(
  2001. data={"release": release.version, "timestamp": self.min_ago},
  2002. project_id=self.project.id,
  2003. )
  2004. query = {"field": ["stack.filename", "release"], "query": "release:test*"}
  2005. response = self.do_request(query)
  2006. assert response.status_code == 200, response.content
  2007. assert len(response.data["data"]) == 1
  2008. assert response.data["data"][0]["release"] == release.version
  2009. def test_transaction_event_type(self):
  2010. project = self.create_project()
  2011. data = load_data(
  2012. "transaction",
  2013. timestamp=before_now(minutes=1),
  2014. start_timestamp=before_now(minutes=1, seconds=5),
  2015. )
  2016. self.store_event(data=data, project_id=project.id)
  2017. query = {
  2018. "field": ["transaction", "transaction.duration", "transaction.status"],
  2019. "query": "event.type:transaction",
  2020. }
  2021. response = self.do_request(query)
  2022. assert response.status_code == 200, response.content
  2023. assert len(response.data["data"]) == 1
  2024. assert response.data["meta"]["transaction.duration"] == "duration"
  2025. assert response.data["meta"]["transaction.status"] == "string"
  2026. assert response.data["data"][0]["transaction.status"] == "ok"
  2027. def test_trace_columns(self):
  2028. project = self.create_project()
  2029. data = load_data(
  2030. "transaction",
  2031. timestamp=before_now(minutes=1),
  2032. start_timestamp=before_now(minutes=1, seconds=5),
  2033. )
  2034. self.store_event(data=data, project_id=project.id)
  2035. query = {"field": ["trace"], "query": "event.type:transaction"}
  2036. response = self.do_request(query)
  2037. assert response.status_code == 200, response.content
  2038. assert len(response.data["data"]) == 1
  2039. assert response.data["meta"]["trace"] == "string"
  2040. assert response.data["data"][0]["trace"] == data["contexts"]["trace"]["trace_id"]
  2041. def test_issue_in_columns(self):
  2042. project1 = self.create_project()
  2043. project2 = self.create_project()
  2044. event1 = self.store_event(
  2045. data={
  2046. "event_id": "a" * 32,
  2047. "transaction": "/example",
  2048. "message": "how to make fast",
  2049. "timestamp": self.two_min_ago,
  2050. "fingerprint": ["group_1"],
  2051. },
  2052. project_id=project1.id,
  2053. )
  2054. event2 = self.store_event(
  2055. data={
  2056. "event_id": "b" * 32,
  2057. "transaction": "/example",
  2058. "message": "how to make fast",
  2059. "timestamp": self.two_min_ago,
  2060. "fingerprint": ["group_1"],
  2061. },
  2062. project_id=project2.id,
  2063. )
  2064. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2065. query = {"field": ["id", "issue"], "orderby": ["id"]}
  2066. response = self.do_request(query, features=features)
  2067. assert response.status_code == 200, response.content
  2068. data = response.data["data"]
  2069. assert len(data) == 2
  2070. assert data[0]["id"] == event1.event_id
  2071. assert data[0]["issue.id"] == event1.group_id
  2072. assert data[0]["issue"] == event1.group.qualified_short_id
  2073. assert data[1]["id"] == event2.event_id
  2074. assert data[1]["issue.id"] == event2.group_id
  2075. assert data[1]["issue"] == event2.group.qualified_short_id
  2076. def test_issue_in_search_and_columns(self):
  2077. project1 = self.create_project()
  2078. project2 = self.create_project()
  2079. event1 = self.store_event(
  2080. data={
  2081. "event_id": "a" * 32,
  2082. "transaction": "/example",
  2083. "message": "how to make fast",
  2084. "timestamp": self.two_min_ago,
  2085. "fingerprint": ["group_1"],
  2086. },
  2087. project_id=project1.id,
  2088. )
  2089. self.store_event(
  2090. data={
  2091. "event_id": "b" * 32,
  2092. "transaction": "/example",
  2093. "message": "how to make fast",
  2094. "timestamp": self.two_min_ago,
  2095. "fingerprint": ["group_1"],
  2096. },
  2097. project_id=project2.id,
  2098. )
  2099. tests = [
  2100. ("issue", "issue:%s" % event1.group.qualified_short_id),
  2101. ("issue.id", "issue:%s" % event1.group.qualified_short_id),
  2102. ("issue", "issue.id:%s" % event1.group_id),
  2103. ("issue.id", "issue.id:%s" % event1.group_id),
  2104. ]
  2105. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2106. for testdata in tests:
  2107. query = {"field": [testdata[0]], "query": testdata[1]}
  2108. response = self.do_request(query, features=features)
  2109. assert response.status_code == 200, response.content
  2110. data = response.data["data"]
  2111. assert len(data) == 1
  2112. assert data[0]["id"] == event1.event_id
  2113. assert data[0]["issue.id"] == event1.group_id
  2114. if testdata[0] == "issue":
  2115. assert data[0]["issue"] == event1.group.qualified_short_id
  2116. else:
  2117. assert data[0].get("issue", None) is None
  2118. def test_issue_negation(self):
  2119. project1 = self.create_project()
  2120. project2 = self.create_project()
  2121. event1 = self.store_event(
  2122. data={
  2123. "event_id": "a" * 32,
  2124. "transaction": "/example",
  2125. "message": "how to make fast",
  2126. "timestamp": self.two_min_ago,
  2127. "fingerprint": ["group_1"],
  2128. },
  2129. project_id=project1.id,
  2130. )
  2131. event2 = self.store_event(
  2132. data={
  2133. "event_id": "b" * 32,
  2134. "transaction": "/example",
  2135. "message": "go really fast plz",
  2136. "timestamp": self.two_min_ago,
  2137. "fingerprint": ["group_2"],
  2138. },
  2139. project_id=project2.id,
  2140. )
  2141. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2142. query = {
  2143. "field": ["title", "issue.id"],
  2144. "query": f"!issue:{event1.group.qualified_short_id}",
  2145. }
  2146. response = self.do_request(query, features=features)
  2147. assert response.status_code == 200, response.content
  2148. data = response.data["data"]
  2149. assert len(data) == 1
  2150. assert data[0]["title"] == event2.title
  2151. assert data[0]["issue.id"] == event2.group_id
  2152. def test_search_for_nonexistent_issue(self):
  2153. project1 = self.create_project()
  2154. self.store_event(
  2155. data={
  2156. "event_id": "a" * 32,
  2157. "transaction": "/example",
  2158. "message": "how to make fast",
  2159. "timestamp": self.two_min_ago,
  2160. "fingerprint": ["group_1"],
  2161. },
  2162. project_id=project1.id,
  2163. )
  2164. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2165. query = {"field": ["count()"], "query": "issue.id:112358"}
  2166. response = self.do_request(query, features=features)
  2167. assert response.status_code == 200, response.content
  2168. data = response.data["data"]
  2169. assert len(data) == 1
  2170. assert data[0]["count"] == 0
  2171. def test_issue_alias_inside_aggregate(self):
  2172. project1 = self.create_project()
  2173. self.store_event(
  2174. data={
  2175. "event_id": "a" * 32,
  2176. "transaction": "/example",
  2177. "message": "how to make fast",
  2178. "timestamp": self.two_min_ago,
  2179. "fingerprint": ["group_1"],
  2180. },
  2181. project_id=project1.id,
  2182. )
  2183. self.store_event(
  2184. data={
  2185. "event_id": "b" * 32,
  2186. "transaction": "/example",
  2187. "message": "how to make fast",
  2188. "timestamp": self.two_min_ago,
  2189. "fingerprint": ["group_2"],
  2190. },
  2191. project_id=project1.id,
  2192. )
  2193. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2194. query = {
  2195. "field": ["project", "count(id)", "count_unique(issue.id)", "count_unique(issue)"],
  2196. "sort": "-count(id)",
  2197. "statsPeriod": "24h",
  2198. }
  2199. response = self.do_request(query, features=features)
  2200. assert response.status_code == 200, response.content
  2201. data = response.data["data"]
  2202. assert len(data) == 1
  2203. assert data[0]["count_id"] == 2
  2204. assert data[0]["count_unique_issue_id"] == 2
  2205. assert data[0]["count_unique_issue"] == 2
  2206. def test_project_alias_inside_aggregate(self):
  2207. project1 = self.create_project()
  2208. project2 = self.create_project()
  2209. self.store_event(
  2210. data={
  2211. "event_id": "a" * 32,
  2212. "transaction": "/example",
  2213. "message": "how to make fast",
  2214. "timestamp": self.two_min_ago,
  2215. "fingerprint": ["group_1"],
  2216. },
  2217. project_id=project1.id,
  2218. )
  2219. self.store_event(
  2220. data={
  2221. "event_id": "b" * 32,
  2222. "transaction": "/example",
  2223. "message": "how to make fast",
  2224. "timestamp": self.two_min_ago,
  2225. "fingerprint": ["group_2"],
  2226. },
  2227. project_id=project2.id,
  2228. )
  2229. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2230. query = {
  2231. "field": [
  2232. "event.type",
  2233. "count(id)",
  2234. "count_unique(project.id)",
  2235. "count_unique(project)",
  2236. ],
  2237. "sort": "-count(id)",
  2238. "statsPeriod": "24h",
  2239. }
  2240. response = self.do_request(query, features=features)
  2241. assert response.status_code == 200, response.content
  2242. data = response.data["data"]
  2243. assert len(data) == 1
  2244. assert data[0]["count_id"] == 2
  2245. assert data[0]["count_unique_project_id"] == 2
  2246. assert data[0]["count_unique_project"] == 2
  2247. def test_user_display(self):
  2248. project1 = self.create_project()
  2249. project2 = self.create_project()
  2250. self.store_event(
  2251. data={
  2252. "event_id": "a" * 32,
  2253. "transaction": "/example",
  2254. "message": "how to make fast",
  2255. "timestamp": self.two_min_ago,
  2256. "user": {"email": "cathy@example.com"},
  2257. },
  2258. project_id=project1.id,
  2259. )
  2260. self.store_event(
  2261. data={
  2262. "event_id": "b" * 32,
  2263. "transaction": "/example",
  2264. "message": "how to make fast",
  2265. "timestamp": self.two_min_ago,
  2266. "user": {"username": "catherine"},
  2267. },
  2268. project_id=project2.id,
  2269. )
  2270. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2271. query = {
  2272. "field": ["event.type", "user.display"],
  2273. "query": "user.display:cath*",
  2274. "statsPeriod": "24h",
  2275. }
  2276. response = self.do_request(query, features=features)
  2277. assert response.status_code == 200, response.content
  2278. data = response.data["data"]
  2279. assert len(data) == 2
  2280. result = {r["user.display"] for r in data}
  2281. assert result == {"catherine", "cathy@example.com"}
  2282. def test_user_display_with_aggregates(self):
  2283. self.login_as(user=self.user)
  2284. project1 = self.create_project()
  2285. self.store_event(
  2286. data={
  2287. "event_id": "a" * 32,
  2288. "transaction": "/example",
  2289. "message": "how to make fast",
  2290. "timestamp": self.two_min_ago,
  2291. "user": {"email": "cathy@example.com"},
  2292. },
  2293. project_id=project1.id,
  2294. )
  2295. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2296. query = {
  2297. "field": ["event.type", "user.display", "count_unique(title)"],
  2298. "statsPeriod": "24h",
  2299. }
  2300. response = self.do_request(query, features=features)
  2301. assert response.status_code == 200, response.content
  2302. data = response.data["data"]
  2303. assert len(data) == 1
  2304. result = {r["user.display"] for r in data}
  2305. assert result == {"cathy@example.com"}
  2306. query = {"field": ["event.type", "count_unique(user.display)"], "statsPeriod": "24h"}
  2307. response = self.do_request(query, features=features)
  2308. assert response.status_code == 200, response.content
  2309. data = response.data["data"]
  2310. assert len(data) == 1
  2311. assert data[0]["count_unique_user_display"] == 1
  2312. def test_orderby_user_display(self):
  2313. project1 = self.create_project()
  2314. project2 = self.create_project()
  2315. self.store_event(
  2316. data={
  2317. "event_id": "a" * 32,
  2318. "transaction": "/example",
  2319. "message": "how to make fast",
  2320. "timestamp": self.two_min_ago,
  2321. "user": {"email": "cathy@example.com"},
  2322. },
  2323. project_id=project1.id,
  2324. )
  2325. self.store_event(
  2326. data={
  2327. "event_id": "b" * 32,
  2328. "transaction": "/example",
  2329. "message": "how to make fast",
  2330. "timestamp": self.two_min_ago,
  2331. "user": {"username": "catherine"},
  2332. },
  2333. project_id=project2.id,
  2334. )
  2335. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2336. query = {
  2337. "field": ["event.type", "user.display"],
  2338. "query": "user.display:cath*",
  2339. "statsPeriod": "24h",
  2340. "orderby": "-user.display",
  2341. }
  2342. response = self.do_request(query, features=features)
  2343. assert response.status_code == 200, response.content
  2344. data = response.data["data"]
  2345. assert len(data) == 2
  2346. result = [r["user.display"] for r in data]
  2347. # because we're ordering by `-user.display`, we expect the results in reverse sorted order
  2348. assert result == ["cathy@example.com", "catherine"]
  2349. def test_orderby_user_display_with_aggregates(self):
  2350. project1 = self.create_project()
  2351. project2 = self.create_project()
  2352. self.store_event(
  2353. data={
  2354. "event_id": "a" * 32,
  2355. "transaction": "/example",
  2356. "message": "how to make fast",
  2357. "timestamp": self.two_min_ago,
  2358. "user": {"email": "cathy@example.com"},
  2359. },
  2360. project_id=project1.id,
  2361. )
  2362. self.store_event(
  2363. data={
  2364. "event_id": "b" * 32,
  2365. "transaction": "/example",
  2366. "message": "how to make fast",
  2367. "timestamp": self.two_min_ago,
  2368. "user": {"username": "catherine"},
  2369. },
  2370. project_id=project2.id,
  2371. )
  2372. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2373. query = {
  2374. "field": ["event.type", "user.display", "count_unique(title)"],
  2375. "query": "user.display:cath*",
  2376. "statsPeriod": "24h",
  2377. "orderby": "user.display",
  2378. }
  2379. response = self.do_request(query, features=features)
  2380. assert response.status_code == 200, response.content
  2381. data = response.data["data"]
  2382. assert len(data) == 2
  2383. result = [r["user.display"] for r in data]
  2384. # because we're ordering by `user.display`, we expect the results in sorted order
  2385. assert result == ["catherine", "cathy@example.com"]
  2386. def test_any_field_alias(self):
  2387. day_ago = before_now(days=1).replace(hour=10, minute=11, second=12, microsecond=13)
  2388. project1 = self.create_project()
  2389. self.store_event(
  2390. data={
  2391. "event_id": "a" * 32,
  2392. "transaction": "/example",
  2393. "message": "how to make fast",
  2394. "timestamp": iso_format(day_ago),
  2395. "user": {"email": "cathy@example.com"},
  2396. },
  2397. project_id=project1.id,
  2398. )
  2399. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2400. query = {
  2401. "field": [
  2402. "event.type",
  2403. "any(user.display)",
  2404. "any(timestamp.to_day)",
  2405. "any(timestamp.to_hour)",
  2406. ],
  2407. "statsPeriod": "7d",
  2408. }
  2409. response = self.do_request(query, features=features)
  2410. assert response.status_code == 200, response.content
  2411. data = response.data["data"]
  2412. assert len(data) == 1
  2413. result = {r["any_user_display"] for r in data}
  2414. assert result == {"cathy@example.com"}
  2415. result = {r["any_timestamp_to_day"][:19] for r in data}
  2416. assert result == {iso_format(day_ago.replace(hour=0, minute=0, second=0, microsecond=0))}
  2417. result = {r["any_timestamp_to_hour"][:19] for r in data}
  2418. assert result == {iso_format(day_ago.replace(minute=0, second=0, microsecond=0))}
  2419. def test_field_aliases_in_conflicting_functions(self):
  2420. day_ago = before_now(days=1).replace(hour=10, minute=11, second=12, microsecond=13)
  2421. project1 = self.create_project()
  2422. self.store_event(
  2423. data={
  2424. "event_id": "a" * 32,
  2425. "transaction": "/example",
  2426. "message": "how to make fast",
  2427. "timestamp": iso_format(day_ago),
  2428. "user": {"email": "cathy@example.com"},
  2429. },
  2430. project_id=project1.id,
  2431. )
  2432. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2433. field_aliases = ["user.display", "timestamp.to_day", "timestamp.to_hour"]
  2434. for alias in field_aliases:
  2435. query = {
  2436. "field": [alias, f"any({alias})"],
  2437. "statsPeriod": "7d",
  2438. }
  2439. response = self.do_request(query, features=features)
  2440. assert response.status_code == 400, response.content
  2441. assert (
  2442. response.data["detail"]
  2443. == f"A single field cannot be used both inside and outside a function in the same query. To use {alias} you must first remove the function(s): any({alias})"
  2444. )
  2445. @pytest.mark.skip(
  2446. """
  2447. For some reason ClickHouse errors when there are two of the same string literals
  2448. (in this case the empty string "") in a query and one is in the prewhere clause.
  2449. Does not affect production or ClickHouse versions > 20.4.
  2450. """
  2451. )
  2452. def test_has_message(self):
  2453. project = self.create_project()
  2454. event = self.store_event(
  2455. {"timestamp": iso_format(before_now(minutes=1)), "message": "a"}, project_id=project.id
  2456. )
  2457. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2458. query = {"field": ["project", "message"], "query": "has:message", "statsPeriod": "14d"}
  2459. response = self.do_request(query, features=features)
  2460. assert response.status_code == 200, response.content
  2461. assert len(response.data["data"]) == 1
  2462. assert response.data["data"][0]["message"] == event.message
  2463. query = {"field": ["project", "message"], "query": "!has:message", "statsPeriod": "14d"}
  2464. response = self.do_request(query, features=features)
  2465. assert response.status_code == 200, response.content
  2466. assert len(response.data["data"]) == 0
  2467. def test_has_transaction_status(self):
  2468. project = self.create_project()
  2469. data = load_data("transaction", timestamp=before_now(minutes=1))
  2470. data["transaction"] = "/transactionstatus/1"
  2471. self.store_event(data, project_id=project.id)
  2472. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2473. query = {
  2474. "field": ["event.type", "count(id)"],
  2475. "query": "event.type:transaction has:transaction.status",
  2476. "sort": "-count(id)",
  2477. "statsPeriod": "24h",
  2478. }
  2479. response = self.do_request(query, features=features)
  2480. assert response.status_code == 200, response.content
  2481. data = response.data["data"]
  2482. assert len(data) == 1
  2483. assert data[0]["count_id"] == 1
  2484. def test_not_has_transaction_status(self):
  2485. project = self.create_project()
  2486. data = load_data("transaction", timestamp=before_now(minutes=1))
  2487. data["transaction"] = "/transactionstatus/1"
  2488. self.store_event(data, project_id=project.id)
  2489. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2490. query = {
  2491. "field": ["event.type", "count(id)"],
  2492. "query": "event.type:transaction !has:transaction.status",
  2493. "sort": "-count(id)",
  2494. "statsPeriod": "24h",
  2495. }
  2496. response = self.do_request(query, features=features)
  2497. assert response.status_code == 200, response.content
  2498. data = response.data["data"]
  2499. assert len(data) == 1
  2500. assert data[0]["count_id"] == 0
  2501. def test_tag_that_looks_like_aggregation(self):
  2502. project = self.create_project()
  2503. data = {
  2504. "message": "Failure state",
  2505. "timestamp": self.two_min_ago,
  2506. "tags": {"count_diff": 99},
  2507. }
  2508. self.store_event(data, project_id=project.id)
  2509. query = {
  2510. "field": ["message", "count_diff", "count()"],
  2511. "query": "",
  2512. "project": [project.id],
  2513. "statsPeriod": "24h",
  2514. }
  2515. response = self.do_request(query)
  2516. assert response.status_code == 200, response.content
  2517. meta = response.data["meta"]
  2518. assert "string" == meta["count_diff"], "tags should not be counted as integers"
  2519. assert "string" == meta["message"]
  2520. assert "integer" == meta["count"]
  2521. assert 1 == len(response.data["data"])
  2522. data = response.data["data"][0]
  2523. assert "99" == data["count_diff"]
  2524. assert "Failure state" == data["message"]
  2525. assert 1 == data["count"]
  2526. def test_aggregate_negation(self):
  2527. project = self.create_project()
  2528. data = load_data(
  2529. "transaction",
  2530. timestamp=before_now(minutes=1),
  2531. start_timestamp=before_now(minutes=1, seconds=5),
  2532. )
  2533. self.store_event(data, project_id=project.id)
  2534. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2535. query = {
  2536. "field": ["event.type", "p99()"],
  2537. "query": "event.type:transaction p99():5s",
  2538. "statsPeriod": "24h",
  2539. }
  2540. response = self.do_request(query, features=features)
  2541. assert response.status_code == 200, response.content
  2542. data = response.data["data"]
  2543. assert len(data) == 1
  2544. query = {
  2545. "field": ["event.type", "p99()"],
  2546. "query": "event.type:transaction !p99():5s",
  2547. "statsPeriod": "24h",
  2548. }
  2549. response = self.do_request(query, features=features)
  2550. assert response.status_code == 200, response.content
  2551. data = response.data["data"]
  2552. assert len(data) == 0
  2553. def test_all_aggregates_in_columns(self):
  2554. project = self.create_project()
  2555. data = load_data(
  2556. "transaction",
  2557. timestamp=before_now(minutes=2),
  2558. start_timestamp=before_now(minutes=2, seconds=5),
  2559. )
  2560. data["transaction"] = "/failure_rate/1"
  2561. self.store_event(data, project_id=project.id)
  2562. data = load_data(
  2563. "transaction",
  2564. timestamp=before_now(minutes=1),
  2565. start_timestamp=before_now(minutes=1, seconds=5),
  2566. )
  2567. data["transaction"] = "/failure_rate/1"
  2568. data["contexts"]["trace"]["status"] = "unauthenticated"
  2569. event = self.store_event(data, project_id=project.id)
  2570. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2571. query = {
  2572. "field": [
  2573. "event.type",
  2574. "p50()",
  2575. "p75()",
  2576. "p95()",
  2577. "p99()",
  2578. "p100()",
  2579. "percentile(transaction.duration, 0.99)",
  2580. "apdex(300)",
  2581. "count_miserable(user, 300)",
  2582. "user_misery(300)",
  2583. "failure_rate()",
  2584. ],
  2585. "query": "event.type:transaction",
  2586. }
  2587. response = self.do_request(query, features=features)
  2588. assert response.status_code == 200, response.content
  2589. meta = response.data["meta"]
  2590. assert meta["p50"] == "duration"
  2591. assert meta["p75"] == "duration"
  2592. assert meta["p95"] == "duration"
  2593. assert meta["p99"] == "duration"
  2594. assert meta["p100"] == "duration"
  2595. assert meta["percentile_transaction_duration_0_99"] == "duration"
  2596. assert meta["apdex_300"] == "number"
  2597. assert meta["failure_rate"] == "percentage"
  2598. assert meta["user_misery_300"] == "number"
  2599. assert meta["count_miserable_user_300"] == "integer"
  2600. data = response.data["data"]
  2601. assert len(data) == 1
  2602. assert data[0]["p50"] == 5000
  2603. assert data[0]["p75"] == 5000
  2604. assert data[0]["p95"] == 5000
  2605. assert data[0]["p99"] == 5000
  2606. assert data[0]["p100"] == 5000
  2607. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2608. assert data[0]["apdex_300"] == 0.0
  2609. assert data[0]["count_miserable_user_300"] == 1
  2610. assert data[0]["user_misery_300"] == 0.058
  2611. assert data[0]["failure_rate"] == 0.5
  2612. features = {
  2613. "organizations:discover-basic": True,
  2614. "organizations:global-views": True,
  2615. }
  2616. query = {
  2617. "field": [
  2618. "event.type",
  2619. "p50()",
  2620. "p75()",
  2621. "p95()",
  2622. "p99()",
  2623. "p100()",
  2624. "percentile(transaction.duration, 0.99)",
  2625. "apdex(300)",
  2626. "apdex()",
  2627. "count_miserable(user, 300)",
  2628. "user_misery(300)",
  2629. "failure_rate()",
  2630. "count_miserable(user)",
  2631. "user_misery()",
  2632. ],
  2633. "query": "event.type:transaction",
  2634. "project": [project.id],
  2635. }
  2636. response = self.do_request(query, features=features)
  2637. assert response.status_code == 200, response.content
  2638. meta = response.data["meta"]
  2639. assert meta["p50"] == "duration"
  2640. assert meta["p75"] == "duration"
  2641. assert meta["p95"] == "duration"
  2642. assert meta["p99"] == "duration"
  2643. assert meta["p100"] == "duration"
  2644. assert meta["percentile_transaction_duration_0_99"] == "duration"
  2645. assert meta["apdex_300"] == "number"
  2646. assert meta["apdex"] == "number"
  2647. assert meta["failure_rate"] == "percentage"
  2648. assert meta["user_misery_300"] == "number"
  2649. assert meta["count_miserable_user_300"] == "integer"
  2650. assert meta["project_threshold_config"] == "string"
  2651. assert meta["user_misery"] == "number"
  2652. assert meta["count_miserable_user"] == "integer"
  2653. data = response.data["data"]
  2654. assert len(data) == 1
  2655. assert data[0]["p50"] == 5000
  2656. assert data[0]["p75"] == 5000
  2657. assert data[0]["p95"] == 5000
  2658. assert data[0]["p99"] == 5000
  2659. assert data[0]["p100"] == 5000
  2660. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2661. assert data[0]["apdex_300"] == 0.0
  2662. assert data[0]["apdex"] == 0.0
  2663. assert data[0]["count_miserable_user_300"] == 1
  2664. assert data[0]["user_misery_300"] == 0.058
  2665. assert data[0]["failure_rate"] == 0.5
  2666. assert data[0]["project_threshold_config"] == ["duration", 300]
  2667. assert data[0]["user_misery"] == 0.058
  2668. assert data[0]["count_miserable_user"] == 1
  2669. query = {
  2670. "field": ["event.type", "last_seen()", "latest_event()"],
  2671. "query": "event.type:transaction",
  2672. }
  2673. response = self.do_request(query, features=features)
  2674. assert response.status_code == 200, response.content
  2675. data = response.data["data"]
  2676. assert len(data) == 1
  2677. assert iso_format(before_now(minutes=1))[:-5] in data[0]["last_seen"]
  2678. assert data[0]["latest_event"] == event.event_id
  2679. query = {
  2680. "field": [
  2681. "event.type",
  2682. "count()",
  2683. "count(id)",
  2684. "count_unique(project)",
  2685. "min(transaction.duration)",
  2686. "max(transaction.duration)",
  2687. "avg(transaction.duration)",
  2688. "stddev(transaction.duration)",
  2689. "var(transaction.duration)",
  2690. "cov(transaction.duration, transaction.duration)",
  2691. "corr(transaction.duration, transaction.duration)",
  2692. "sum(transaction.duration)",
  2693. ],
  2694. "query": "event.type:transaction",
  2695. }
  2696. response = self.do_request(query, features=features)
  2697. assert response.status_code == 200, response.content
  2698. data = response.data["data"]
  2699. assert len(data) == 1
  2700. assert data[0]["count"] == 2
  2701. assert data[0]["count_id"] == 2
  2702. assert data[0]["count_unique_project"] == 1
  2703. assert data[0]["min_transaction_duration"] == 5000
  2704. assert data[0]["max_transaction_duration"] == 5000
  2705. assert data[0]["avg_transaction_duration"] == 5000
  2706. assert data[0]["stddev_transaction_duration"] == 0.0
  2707. assert data[0]["var_transaction_duration"] == 0.0
  2708. assert data[0]["cov_transaction_duration_transaction_duration"] == 0.0
  2709. assert data[0]["corr_transaction_duration_transaction_duration"] == 0.0
  2710. assert data[0]["sum_transaction_duration"] == 10000
  2711. @requires_not_arm64
  2712. def test_null_user_misery_returns_zero(self):
  2713. project = self.create_project()
  2714. data = load_data(
  2715. "transaction",
  2716. timestamp=before_now(minutes=2),
  2717. start_timestamp=before_now(minutes=2, seconds=5),
  2718. )
  2719. data["user"] = None
  2720. data["transaction"] = "/no_users/1"
  2721. self.store_event(data, project_id=project.id)
  2722. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2723. query = {
  2724. "field": ["user_misery(300)"],
  2725. "query": "event.type:transaction",
  2726. }
  2727. response = self.do_request(query, features=features)
  2728. assert response.status_code == 200, response.content
  2729. meta = response.data["meta"]
  2730. assert meta["user_misery_300"] == "number"
  2731. data = response.data["data"]
  2732. assert data[0]["user_misery_300"] == 0
  2733. @requires_not_arm64
  2734. def test_null_user_misery_new_returns_zero(self):
  2735. project = self.create_project()
  2736. data = load_data(
  2737. "transaction",
  2738. timestamp=before_now(minutes=2),
  2739. start_timestamp=before_now(minutes=2, seconds=5),
  2740. )
  2741. data["user"] = None
  2742. data["transaction"] = "/no_users/1"
  2743. self.store_event(data, project_id=project.id)
  2744. features = {
  2745. "organizations:discover-basic": True,
  2746. }
  2747. query = {
  2748. "field": ["user_misery()"],
  2749. "query": "event.type:transaction",
  2750. }
  2751. response = self.do_request(query, features=features)
  2752. assert response.status_code == 200, response.content
  2753. meta = response.data["meta"]
  2754. assert meta["user_misery"] == "number"
  2755. data = response.data["data"]
  2756. assert data[0]["user_misery"] == 0
  2757. def test_all_aggregates_in_query(self):
  2758. project = self.create_project()
  2759. data = load_data(
  2760. "transaction",
  2761. timestamp=before_now(minutes=2),
  2762. start_timestamp=before_now(minutes=2, seconds=5),
  2763. )
  2764. data["transaction"] = "/failure_rate/1"
  2765. self.store_event(data, project_id=project.id)
  2766. data = load_data(
  2767. "transaction",
  2768. timestamp=before_now(minutes=1),
  2769. start_timestamp=before_now(minutes=1, seconds=5),
  2770. )
  2771. data["transaction"] = "/failure_rate/2"
  2772. data["contexts"]["trace"]["status"] = "unauthenticated"
  2773. self.store_event(data, project_id=project.id)
  2774. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2775. query = {
  2776. "field": [
  2777. "event.type",
  2778. "p50()",
  2779. "p75()",
  2780. "p95()",
  2781. "percentile(transaction.duration, 0.99)",
  2782. "p100()",
  2783. ],
  2784. "query": "event.type:transaction p50():>100 p75():>1000 p95():>1000 p100():>1000 percentile(transaction.duration, 0.99):>1000",
  2785. }
  2786. response = self.do_request(query, features=features)
  2787. assert response.status_code == 200, response.content
  2788. data = response.data["data"]
  2789. assert len(data) == 1
  2790. assert data[0]["p50"] == 5000
  2791. assert data[0]["p75"] == 5000
  2792. assert data[0]["p95"] == 5000
  2793. assert data[0]["p100"] == 5000
  2794. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2795. query = {
  2796. "field": [
  2797. "event.type",
  2798. "apdex(300)",
  2799. "count_miserable(user, 300)",
  2800. "user_misery(300)",
  2801. "failure_rate()",
  2802. ],
  2803. "query": "event.type:transaction apdex(300):>-1.0 failure_rate():>0.25",
  2804. }
  2805. response = self.do_request(query, features=features)
  2806. assert response.status_code == 200, response.content
  2807. data = response.data["data"]
  2808. assert len(data) == 1
  2809. assert data[0]["apdex_300"] == 0.0
  2810. assert data[0]["count_miserable_user_300"] == 1
  2811. assert data[0]["user_misery_300"] == 0.058
  2812. assert data[0]["failure_rate"] == 0.5
  2813. query = {
  2814. "field": ["event.type", "last_seen()", "latest_event()"],
  2815. "query": "event.type:transaction last_seen():>1990-12-01T00:00:00",
  2816. }
  2817. response = self.do_request(query, features=features)
  2818. assert response.status_code == 200, response.content
  2819. data = response.data["data"]
  2820. assert len(data) == 1
  2821. query = {
  2822. "field": ["event.type", "count()", "count(id)", "count_unique(transaction)"],
  2823. "query": "event.type:transaction count():>1 count(id):>1 count_unique(transaction):>1",
  2824. }
  2825. response = self.do_request(query, features=features)
  2826. assert response.status_code == 200, response.content
  2827. data = response.data["data"]
  2828. assert len(data) == 1
  2829. assert data[0]["count"] == 2
  2830. assert data[0]["count_id"] == 2
  2831. assert data[0]["count_unique_transaction"] == 2
  2832. query = {
  2833. "field": [
  2834. "event.type",
  2835. "min(transaction.duration)",
  2836. "max(transaction.duration)",
  2837. "avg(transaction.duration)",
  2838. "sum(transaction.duration)",
  2839. "stddev(transaction.duration)",
  2840. "var(transaction.duration)",
  2841. "cov(transaction.duration, transaction.duration)",
  2842. "corr(transaction.duration, transaction.duration)",
  2843. ],
  2844. "query": " ".join(
  2845. [
  2846. "event.type:transaction",
  2847. "min(transaction.duration):>1000",
  2848. "max(transaction.duration):>1000",
  2849. "avg(transaction.duration):>1000",
  2850. "sum(transaction.duration):>1000",
  2851. "stddev(transaction.duration):>=0.0",
  2852. "var(transaction.duration):>=0.0",
  2853. "cov(transaction.duration, transaction.duration):>=0.0",
  2854. # correlation is nan because variance is 0
  2855. # "corr(transaction.duration, transaction.duration):>=0.0",
  2856. ]
  2857. ),
  2858. }
  2859. response = self.do_request(query, features=features)
  2860. assert response.status_code == 200, response.content
  2861. data = response.data["data"]
  2862. assert len(data) == 1
  2863. assert data[0]["min_transaction_duration"] == 5000
  2864. assert data[0]["max_transaction_duration"] == 5000
  2865. assert data[0]["avg_transaction_duration"] == 5000
  2866. assert data[0]["sum_transaction_duration"] == 10000
  2867. assert data[0]["stddev_transaction_duration"] == 0.0
  2868. assert data[0]["var_transaction_duration"] == 0.0
  2869. assert data[0]["cov_transaction_duration_transaction_duration"] == 0.0
  2870. assert data[0]["corr_transaction_duration_transaction_duration"] == 0.0
  2871. query = {
  2872. "field": ["event.type", "apdex(400)"],
  2873. "query": "event.type:transaction apdex(400):0",
  2874. }
  2875. response = self.do_request(query, features=features)
  2876. assert response.status_code == 200, response.content
  2877. data = response.data["data"]
  2878. assert len(data) == 1
  2879. assert data[0]["apdex_400"] == 0
  2880. def test_functions_in_orderby(self):
  2881. project = self.create_project()
  2882. data = load_data(
  2883. "transaction",
  2884. timestamp=before_now(minutes=2),
  2885. start_timestamp=before_now(minutes=2, seconds=5),
  2886. )
  2887. data["transaction"] = "/failure_rate/1"
  2888. self.store_event(data, project_id=project.id)
  2889. data = load_data(
  2890. "transaction",
  2891. timestamp=before_now(minutes=1),
  2892. start_timestamp=before_now(minutes=1, seconds=5),
  2893. )
  2894. data["transaction"] = "/failure_rate/2"
  2895. data["contexts"]["trace"]["status"] = "unauthenticated"
  2896. event = self.store_event(data, project_id=project.id)
  2897. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2898. query = {
  2899. "field": ["event.type", "p75()"],
  2900. "sort": "-p75",
  2901. "query": "event.type:transaction",
  2902. }
  2903. response = self.do_request(query, features=features)
  2904. assert response.status_code == 200, response.content
  2905. data = response.data["data"]
  2906. assert len(data) == 1
  2907. assert data[0]["p75"] == 5000
  2908. query = {
  2909. "field": ["event.type", "percentile(transaction.duration, 0.99)"],
  2910. "sort": "-percentile_transaction_duration_0_99",
  2911. "query": "event.type:transaction",
  2912. }
  2913. response = self.do_request(query, features=features)
  2914. assert response.status_code == 200, response.content
  2915. data = response.data["data"]
  2916. assert len(data) == 1
  2917. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2918. query = {
  2919. "field": ["event.type", "apdex(300)"],
  2920. "sort": "-apdex(300)",
  2921. "query": "event.type:transaction",
  2922. }
  2923. response = self.do_request(query, features=features)
  2924. assert response.status_code == 200, response.content
  2925. data = response.data["data"]
  2926. assert len(data) == 1
  2927. assert data[0]["apdex_300"] == 0.0
  2928. query = {
  2929. "field": ["event.type", "latest_event()"],
  2930. "query": "event.type:transaction",
  2931. "sort": "latest_event",
  2932. }
  2933. response = self.do_request(query, features=features)
  2934. assert response.status_code == 200, response.content
  2935. data = response.data["data"]
  2936. assert len(data) == 1
  2937. assert data[0]["latest_event"] == event.event_id
  2938. query = {
  2939. "field": ["event.type", "count_unique(transaction)"],
  2940. "query": "event.type:transaction",
  2941. "sort": "-count_unique_transaction",
  2942. }
  2943. response = self.do_request(query, features=features)
  2944. assert response.status_code == 200, response.content
  2945. data = response.data["data"]
  2946. assert len(data) == 1
  2947. assert data[0]["count_unique_transaction"] == 2
  2948. query = {
  2949. "field": ["event.type", "min(transaction.duration)"],
  2950. "query": "event.type:transaction",
  2951. "sort": "-min_transaction_duration",
  2952. }
  2953. response = self.do_request(query, features=features)
  2954. assert response.status_code == 200, response.content
  2955. data = response.data["data"]
  2956. assert len(data) == 1
  2957. assert data[0]["min_transaction_duration"] == 5000
  2958. def test_issue_alias_in_aggregate(self):
  2959. project = self.create_project()
  2960. self.store_event(
  2961. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2962. project_id=project.id,
  2963. )
  2964. self.store_event(
  2965. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  2966. project_id=project.id,
  2967. )
  2968. query = {"field": ["event.type", "count_unique(issue)"], "query": "count_unique(issue):>1"}
  2969. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2970. response = self.do_request(query, features=features)
  2971. assert response.status_code == 200, response.content
  2972. data = response.data["data"]
  2973. assert len(data) == 1
  2974. assert data[0]["count_unique_issue"] == 2
  2975. def test_deleted_issue_in_results(self):
  2976. project = self.create_project()
  2977. event1 = self.store_event(
  2978. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2979. project_id=project.id,
  2980. )
  2981. event2 = self.store_event(
  2982. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  2983. project_id=project.id,
  2984. )
  2985. event2.group.delete()
  2986. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2987. query = {"field": ["issue", "count()"], "sort": "issue.id"}
  2988. response = self.do_request(query, features=features)
  2989. assert response.status_code == 200, response.content
  2990. data = response.data["data"]
  2991. assert len(data) == 2
  2992. assert data[0]["issue"] == event1.group.qualified_short_id
  2993. assert data[1]["issue"] == "unknown"
  2994. def test_last_seen_negative_duration(self):
  2995. project = self.create_project()
  2996. self.store_event(
  2997. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2998. project_id=project.id,
  2999. )
  3000. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  3001. query = {"field": ["id", "last_seen()"], "query": "last_seen():-30d"}
  3002. response = self.do_request(query, features=features)
  3003. assert response.status_code == 200, response.content
  3004. data = response.data["data"]
  3005. assert len(data) == 1
  3006. assert data[0]["id"] == "f" * 32
  3007. def test_last_seen_aggregate_condition(self):
  3008. project = self.create_project()
  3009. self.store_event(
  3010. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  3011. project_id=project.id,
  3012. )
  3013. query = {
  3014. "field": ["id", "last_seen()"],
  3015. "query": f"last_seen():>{iso_format(before_now(days=30))}",
  3016. }
  3017. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  3018. response = self.do_request(query, features=features)
  3019. assert response.status_code == 200, response.content
  3020. data = response.data["data"]
  3021. assert len(data) == 1
  3022. assert data[0]["id"] == "f" * 32
  3023. def test_conditional_filter(self):
  3024. project = self.create_project()
  3025. for v in ["a", "b"]:
  3026. self.store_event(
  3027. data={
  3028. "event_id": v * 32,
  3029. "timestamp": self.two_min_ago,
  3030. "fingerprint": ["group_1"],
  3031. },
  3032. project_id=project.id,
  3033. )
  3034. query = {
  3035. "field": ["id"],
  3036. "query": "id:{} OR id:{}".format("a" * 32, "b" * 32),
  3037. "orderby": "id",
  3038. }
  3039. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  3040. response = self.do_request(query, features=features)
  3041. assert response.status_code == 200, response.content
  3042. data = response.data["data"]
  3043. assert len(data) == 2
  3044. assert data[0]["id"] == "a" * 32
  3045. assert data[1]["id"] == "b" * 32
  3046. def test_aggregation_comparison_with_conditional_filter(self):
  3047. project = self.create_project()
  3048. self.store_event(
  3049. data={
  3050. "event_id": "a" * 32,
  3051. "timestamp": self.min_ago,
  3052. "fingerprint": ["group_1"],
  3053. "user": {"email": "foo@example.com"},
  3054. "environment": "prod",
  3055. },
  3056. project_id=project.id,
  3057. )
  3058. self.store_event(
  3059. data={
  3060. "event_id": "b" * 32,
  3061. "timestamp": self.min_ago,
  3062. "fingerprint": ["group_2"],
  3063. "user": {"email": "foo@example.com"},
  3064. "environment": "staging",
  3065. },
  3066. project_id=project.id,
  3067. )
  3068. event = self.store_event(
  3069. data={
  3070. "event_id": "c" * 32,
  3071. "timestamp": self.min_ago,
  3072. "fingerprint": ["group_2"],
  3073. "user": {"email": "foo@example.com"},
  3074. "environment": "prod",
  3075. },
  3076. project_id=project.id,
  3077. )
  3078. self.store_event(
  3079. data={
  3080. "event_id": "d" * 32,
  3081. "timestamp": self.min_ago,
  3082. "fingerprint": ["group_2"],
  3083. "user": {"email": "foo@example.com"},
  3084. "environment": "canary",
  3085. },
  3086. project_id=project.id,
  3087. )
  3088. query = {
  3089. "field": ["issue.id", "count(id)"],
  3090. "query": "count(id):>1 user.email:foo@example.com AND (environment:prod OR environment:staging)",
  3091. "orderby": "issue.id",
  3092. }
  3093. response = self.do_request(query)
  3094. assert response.status_code == 200, response.content
  3095. assert len(response.data["data"]) == 1
  3096. data = response.data["data"]
  3097. assert data[0]["issue.id"] == event.group_id
  3098. assert data[0]["count_id"] == 2
  3099. def run_test_in_query(self, query, expected_events, expected_negative_events=None):
  3100. params = {
  3101. "field": ["id"],
  3102. "query": query,
  3103. "orderby": "id",
  3104. }
  3105. response = self.do_request(
  3106. params, {"organizations:discover-basic": True, "organizations:global-views": True}
  3107. )
  3108. assert response.status_code == 200, response.content
  3109. assert [row["id"] for row in response.data["data"]] == [e.event_id for e in expected_events]
  3110. if expected_negative_events is not None:
  3111. params["query"] = f"!{query}"
  3112. response = self.do_request(
  3113. params,
  3114. {"organizations:discover-basic": True, "organizations:global-views": True},
  3115. )
  3116. assert response.status_code == 200, response.content
  3117. assert [row["id"] for row in response.data["data"]] == [
  3118. e.event_id for e in expected_negative_events
  3119. ]
  3120. def test_in_query_events(self):
  3121. project_1 = self.create_project()
  3122. event_1 = self.store_event(
  3123. data={
  3124. "event_id": "a" * 32,
  3125. "timestamp": self.min_ago,
  3126. "fingerprint": ["group_1"],
  3127. "message": "group1",
  3128. "user": {"email": "hello@example.com"},
  3129. "environment": "prod",
  3130. "tags": {"random": "123"},
  3131. "release": "1.0",
  3132. },
  3133. project_id=project_1.id,
  3134. )
  3135. project_2 = self.create_project()
  3136. event_2 = self.store_event(
  3137. data={
  3138. "event_id": "b" * 32,
  3139. "timestamp": self.min_ago,
  3140. "fingerprint": ["group_2"],
  3141. "message": "group2",
  3142. "user": {"email": "bar@example.com"},
  3143. "environment": "staging",
  3144. "tags": {"random": "456"},
  3145. "stacktrace": {"frames": [{"filename": "src/app/group2.py"}]},
  3146. "release": "1.2",
  3147. },
  3148. project_id=project_2.id,
  3149. )
  3150. project_3 = self.create_project()
  3151. event_3 = self.store_event(
  3152. data={
  3153. "event_id": "c" * 32,
  3154. "timestamp": self.min_ago,
  3155. "fingerprint": ["group_3"],
  3156. "message": "group3",
  3157. "user": {"email": "foo@example.com"},
  3158. "environment": "canary",
  3159. "tags": {"random": "789"},
  3160. },
  3161. project_id=project_3.id,
  3162. )
  3163. self.run_test_in_query("environment:[prod, staging]", [event_1, event_2], [event_3])
  3164. self.run_test_in_query("environment:[staging]", [event_2], [event_1, event_3])
  3165. self.run_test_in_query(
  3166. "user.email:[foo@example.com, hello@example.com]", [event_1, event_3], [event_2]
  3167. )
  3168. self.run_test_in_query("user.email:[foo@example.com]", [event_3], [event_1, event_2])
  3169. self.run_test_in_query(
  3170. "user.display:[foo@example.com, hello@example.com]", [event_1, event_3], [event_2]
  3171. )
  3172. self.run_test_in_query(
  3173. 'message:["group2 src/app/group2.py in ?", group1]', [event_1, event_2], [event_3]
  3174. )
  3175. self.run_test_in_query(
  3176. f"issue.id:[{event_1.group_id},{event_2.group_id}]", [event_1, event_2]
  3177. )
  3178. self.run_test_in_query(
  3179. f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}]",
  3180. [event_1, event_2],
  3181. )
  3182. self.run_test_in_query(
  3183. f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}, unknown]",
  3184. [event_1, event_2],
  3185. )
  3186. self.run_test_in_query(f"project_id:[{project_3.id},{project_2.id}]", [event_2, event_3])
  3187. self.run_test_in_query(
  3188. f"project.name:[{project_3.slug},{project_2.slug}]", [event_2, event_3]
  3189. )
  3190. self.run_test_in_query("random:[789,456]", [event_2, event_3], [event_1])
  3191. self.run_test_in_query("tags[random]:[789,456]", [event_2, event_3], [event_1])
  3192. self.run_test_in_query("release:[1.0,1.2]", [event_1, event_2], [event_3])
  3193. def test_in_query_events_stack(self):
  3194. project_1 = self.create_project()
  3195. test_js = self.store_event(
  3196. load_data(
  3197. "javascript",
  3198. timestamp=before_now(minutes=1),
  3199. start_timestamp=before_now(minutes=1, seconds=5),
  3200. ),
  3201. project_id=project_1.id,
  3202. )
  3203. test_java = self.store_event(
  3204. load_data(
  3205. "java",
  3206. timestamp=before_now(minutes=1),
  3207. start_timestamp=before_now(minutes=1, seconds=5),
  3208. ),
  3209. project_id=project_1.id,
  3210. )
  3211. self.run_test_in_query(
  3212. "stack.filename:[../../sentry/scripts/views.js]", [test_js], [test_java]
  3213. )
  3214. def test_in_query_transactions(self):
  3215. project = self.create_project()
  3216. data = load_data(
  3217. "transaction",
  3218. timestamp=before_now(minutes=1),
  3219. start_timestamp=before_now(minutes=1, seconds=5),
  3220. )
  3221. data["event_id"] = "a" * 32
  3222. data["contexts"]["trace"]["status"] = "ok"
  3223. transaction_1 = self.store_event(data, project_id=project.id)
  3224. data = load_data(
  3225. "transaction",
  3226. timestamp=before_now(minutes=1),
  3227. start_timestamp=before_now(minutes=1, seconds=5),
  3228. )
  3229. data["event_id"] = "b" * 32
  3230. data["contexts"]["trace"]["status"] = "aborted"
  3231. transaction_2 = self.store_event(data, project_id=project.id)
  3232. data = load_data(
  3233. "transaction",
  3234. timestamp=before_now(minutes=1),
  3235. start_timestamp=before_now(minutes=1, seconds=5),
  3236. )
  3237. data["event_id"] = "c" * 32
  3238. data["contexts"]["trace"]["status"] = "already_exists"
  3239. transaction_3 = self.store_event(data, project_id=project.id)
  3240. self.run_test_in_query(
  3241. "transaction.status:[aborted, already_exists]",
  3242. [transaction_2, transaction_3],
  3243. [transaction_1],
  3244. )
  3245. def test_messed_up_function_values(self):
  3246. # TODO (evanh): It would be nice if this surfaced an error to the user.
  3247. # The problem: The && causes the parser to treat that term not as a bad
  3248. # function call but a valid raw search with parens in it. It's not trivial
  3249. # to change the parser to recognize "bad function values" and surface them.
  3250. project = self.create_project()
  3251. for v in ["a", "b"]:
  3252. self.store_event(
  3253. data={
  3254. "event_id": v * 32,
  3255. "timestamp": self.two_min_ago,
  3256. "fingerprint": ["group_1"],
  3257. },
  3258. project_id=project.id,
  3259. )
  3260. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  3261. query = {
  3262. "field": [
  3263. "transaction",
  3264. "project",
  3265. "epm()",
  3266. "p50()",
  3267. "p95()",
  3268. "failure_rate()",
  3269. "apdex(300)",
  3270. "count_unique(user)",
  3271. "user_misery(300)",
  3272. "count_miserable(user, 300)",
  3273. ],
  3274. "query": "failure_rate():>0.003&& users:>10 event.type:transaction",
  3275. "sort": "-failure_rate",
  3276. "statsPeriod": "24h",
  3277. }
  3278. response = self.do_request(query, features=features)
  3279. assert response.status_code == 200, response.content
  3280. data = response.data["data"]
  3281. assert len(data) == 0
  3282. def test_context_fields_between_datasets(self):
  3283. project = self.create_project()
  3284. event_data = load_data("android")
  3285. transaction_data = load_data("transaction")
  3286. event_data["spans"] = transaction_data["spans"]
  3287. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  3288. event_data["type"] = "transaction"
  3289. event_data["transaction"] = "/failure_rate/1"
  3290. event_data["timestamp"] = iso_format(before_now(minutes=1))
  3291. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  3292. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  3293. self.store_event(event_data, project_id=project.id)
  3294. event_data["type"] = "error"
  3295. self.store_event(event_data, project_id=project.id)
  3296. fields = [
  3297. "os.build",
  3298. "os.kernel_version",
  3299. "device.arch",
  3300. # TODO: battery level is not consistent across both datasets
  3301. # "device.battery_level",
  3302. "device.brand",
  3303. "device.charging",
  3304. "device.locale",
  3305. "device.model_id",
  3306. "device.name",
  3307. "device.online",
  3308. "device.orientation",
  3309. "device.simulator",
  3310. "device.uuid",
  3311. ]
  3312. data = [
  3313. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  3314. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  3315. ]
  3316. for datum in data:
  3317. response = self.do_request(datum)
  3318. assert response.status_code == 200, response.content
  3319. assert len(response.data["data"]) == 1, datum
  3320. results = response.data["data"]
  3321. assert results[0]["count"] == 1, datum
  3322. for field in fields:
  3323. key, value = field.split(".", 1)
  3324. expected = str(event_data["contexts"][key][value])
  3325. assert results[0][field] == expected, field + str(datum)
  3326. def test_http_fields_between_datasets(self):
  3327. project = self.create_project()
  3328. event_data = load_data("android")
  3329. transaction_data = load_data("transaction")
  3330. event_data["spans"] = transaction_data["spans"]
  3331. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  3332. event_data["type"] = "transaction"
  3333. event_data["transaction"] = "/failure_rate/1"
  3334. event_data["timestamp"] = iso_format(before_now(minutes=1))
  3335. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  3336. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  3337. event_data["request"] = transaction_data["request"]
  3338. self.store_event(event_data, project_id=project.id)
  3339. event_data["type"] = "error"
  3340. self.store_event(event_data, project_id=project.id)
  3341. fields = ["http.method", "http.referer", "http.url"]
  3342. expected = ["GET", "fixtures.transaction", "http://countries:8010/country_by_code/"]
  3343. data = [
  3344. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  3345. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  3346. ]
  3347. for datum in data:
  3348. response = self.do_request(datum)
  3349. assert response.status_code == 200, response.content
  3350. assert len(response.data["data"]) == 1, datum
  3351. results = response.data["data"]
  3352. assert results[0]["count"] == 1, datum
  3353. for (field, exp) in zip(fields, expected):
  3354. assert results[0][field] == exp, field + str(datum)
  3355. def test_failure_count_alias_field(self):
  3356. project = self.create_project()
  3357. data = load_data("transaction", timestamp=before_now(minutes=1))
  3358. data["transaction"] = "/failure_count/success"
  3359. self.store_event(data, project_id=project.id)
  3360. data = load_data("transaction", timestamp=before_now(minutes=1))
  3361. data["transaction"] = "/failure_count/unknown"
  3362. data["contexts"]["trace"]["status"] = "unknown_error"
  3363. self.store_event(data, project_id=project.id)
  3364. for i in range(6):
  3365. data = load_data("transaction", timestamp=before_now(minutes=1))
  3366. data["transaction"] = f"/failure_count/{i}"
  3367. data["contexts"]["trace"]["status"] = "unauthenticated"
  3368. self.store_event(data, project_id=project.id)
  3369. query = {"field": ["count()", "failure_count()"], "query": "event.type:transaction"}
  3370. response = self.do_request(query)
  3371. assert response.status_code == 200, response.content
  3372. assert len(response.data["data"]) == 1
  3373. data = response.data["data"]
  3374. assert data[0]["count"] == 8
  3375. assert data[0]["failure_count"] == 6
  3376. @mock.patch("sentry.utils.snuba.quantize_time")
  3377. def test_quantize_dates(self, mock_quantize):
  3378. self.create_project()
  3379. mock_quantize.return_value = before_now(days=1).replace(tzinfo=utc)
  3380. # Don't quantize short time periods
  3381. query = {"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]}
  3382. self.do_request(query)
  3383. # Don't quantize absolute date periods
  3384. self.do_request(query)
  3385. query = {
  3386. "start": iso_format(before_now(days=20)),
  3387. "end": iso_format(before_now(days=15)),
  3388. "query": "",
  3389. "field": ["id", "timestamp"],
  3390. }
  3391. self.do_request(query)
  3392. assert len(mock_quantize.mock_calls) == 0
  3393. # Quantize long date periods
  3394. query = {"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""}
  3395. self.do_request(query)
  3396. assert len(mock_quantize.mock_calls) == 2
  3397. @mock.patch("sentry.snuba.discover.query")
  3398. def test_valid_referrer(self, mock):
  3399. mock.return_value = {}
  3400. project = self.create_project()
  3401. data = load_data("transaction", timestamp=before_now(hours=1))
  3402. self.store_event(data=data, project_id=project.id)
  3403. query = {
  3404. "field": ["user"],
  3405. "referrer": "api.performance.transaction-summary",
  3406. }
  3407. self.do_request(query)
  3408. _, kwargs = mock.call_args
  3409. self.assertEqual(kwargs["referrer"], "api.performance.transaction-summary")
  3410. @mock.patch("sentry.snuba.discover.query")
  3411. def test_invalid_referrer(self, mock):
  3412. mock.return_value = {}
  3413. project = self.create_project()
  3414. data = load_data("transaction", timestamp=before_now(hours=1))
  3415. self.store_event(data=data, project_id=project.id)
  3416. query = {
  3417. "field": ["user"],
  3418. "referrer": "api.performance.invalid",
  3419. }
  3420. self.do_request(query)
  3421. _, kwargs = mock.call_args
  3422. self.assertEqual(kwargs["referrer"], "api.organization-events-v2")
  3423. @mock.patch("sentry.snuba.discover.query")
  3424. def test_empty_referrer(self, mock):
  3425. mock.return_value = {}
  3426. project = self.create_project()
  3427. data = load_data("transaction", timestamp=before_now(hours=1))
  3428. self.store_event(data=data, project_id=project.id)
  3429. query = {
  3430. "field": ["user"],
  3431. }
  3432. self.do_request(query)
  3433. _, kwargs = mock.call_args
  3434. self.assertEqual(kwargs["referrer"], "api.organization-events-v2")
  3435. def test_limit_number_of_fields(self):
  3436. self.create_project()
  3437. for i in range(1, 25):
  3438. response = self.do_request({"field": ["id"] * i})
  3439. if i <= 20:
  3440. assert response.status_code == 200
  3441. else:
  3442. assert response.status_code == 400
  3443. assert (
  3444. response.data["detail"]
  3445. == "You can view up to 20 fields at a time. Please delete some and try again."
  3446. )
  3447. def test_percentile_function_meta_types(self):
  3448. project = self.create_project()
  3449. data = load_data(
  3450. "transaction",
  3451. timestamp=before_now(minutes=1),
  3452. start_timestamp=before_now(minutes=1, seconds=5),
  3453. )
  3454. self.store_event(data, project_id=project.id)
  3455. query = {
  3456. "field": [
  3457. "transaction",
  3458. "percentile(transaction.duration, 0.95)",
  3459. "percentile(measurements.fp, 0.95)",
  3460. "percentile(measurements.fcp, 0.95)",
  3461. "percentile(measurements.lcp, 0.95)",
  3462. "percentile(measurements.fid, 0.95)",
  3463. "percentile(measurements.ttfb, 0.95)",
  3464. "percentile(measurements.ttfb.requesttime, 0.95)",
  3465. "percentile(measurements.cls, 0.95)",
  3466. "percentile(measurements.foo, 0.95)",
  3467. "percentile(measurements.bar, 0.95)",
  3468. ],
  3469. "query": "",
  3470. "orderby": ["transaction"],
  3471. }
  3472. response = self.do_request(query)
  3473. assert response.status_code == 200, response.content
  3474. meta = response.data["meta"]
  3475. assert meta["percentile_transaction_duration_0_95"] == "duration"
  3476. assert meta["percentile_measurements_fp_0_95"] == "duration"
  3477. assert meta["percentile_measurements_fcp_0_95"] == "duration"
  3478. assert meta["percentile_measurements_lcp_0_95"] == "duration"
  3479. assert meta["percentile_measurements_fid_0_95"] == "duration"
  3480. assert meta["percentile_measurements_ttfb_0_95"] == "duration"
  3481. assert meta["percentile_measurements_ttfb_requesttime_0_95"] == "duration"
  3482. assert meta["percentile_measurements_cls_0_95"] == "number"
  3483. assert meta["percentile_measurements_foo_0_95"] == "number"
  3484. assert meta["percentile_measurements_bar_0_95"] == "number"
  3485. def test_count_at_least_query(self):
  3486. self.store_event(self.transaction_data, self.project.id)
  3487. response = self.do_request({"field": "count_at_least(measurements.fcp, 0)"})
  3488. assert response.status_code == 200
  3489. assert len(response.data["data"]) == 1
  3490. assert response.data["data"][0]["count_at_least_measurements_fcp_0"] == 1
  3491. # a value that's a little bigger than the stored fcp
  3492. fcp = int(self.transaction_data["measurements"]["fcp"]["value"] + 1)
  3493. response = self.do_request({"field": f"count_at_least(measurements.fcp, {fcp})"})
  3494. assert response.status_code == 200
  3495. assert len(response.data["data"]) == 1
  3496. assert response.data["data"][0][f"count_at_least_measurements_fcp_{fcp}"] == 0
  3497. def test_measurements_query(self):
  3498. self.store_event(self.transaction_data, self.project.id)
  3499. query = {
  3500. "field": [
  3501. "measurements.fp",
  3502. "measurements.fcp",
  3503. "measurements.lcp",
  3504. "measurements.fid",
  3505. ]
  3506. }
  3507. response = self.do_request(query)
  3508. assert response.status_code == 200, response.content
  3509. assert len(response.data["data"]) == 1
  3510. for field in query["field"]:
  3511. measure = field.split(".", 1)[1]
  3512. assert (
  3513. response.data["data"][0][field]
  3514. == self.transaction_data["measurements"][measure]["value"]
  3515. )
  3516. query = {
  3517. "field": [
  3518. "measurements.fP",
  3519. "measurements.Fcp",
  3520. "measurements.LcP",
  3521. "measurements.FID",
  3522. ]
  3523. }
  3524. response = self.do_request(query)
  3525. assert response.status_code == 200, response.content
  3526. assert len(response.data["data"]) == 1
  3527. for field in query["field"]:
  3528. measure = field.split(".", 1)[1].lower()
  3529. assert (
  3530. response.data["data"][0][field]
  3531. == self.transaction_data["measurements"][measure]["value"]
  3532. )
  3533. def test_measurements_aggregations(self):
  3534. self.store_event(self.transaction_data, self.project.id)
  3535. # should try all the potential aggregates
  3536. # Skipped tests for stddev and var since sampling one data point
  3537. # results in nan.
  3538. query = {
  3539. "field": [
  3540. "percentile(measurements.fcp, 0.5)",
  3541. "count_unique(measurements.fcp)",
  3542. "min(measurements.fcp)",
  3543. "max(measurements.fcp)",
  3544. "avg(measurements.fcp)",
  3545. "sum(measurements.fcp)",
  3546. ],
  3547. }
  3548. response = self.do_request(query)
  3549. assert response.status_code == 200, response.content
  3550. assert len(response.data["data"]) == 1
  3551. assert (
  3552. response.data["data"][0]["percentile_measurements_fcp_0_5"]
  3553. == self.transaction_data["measurements"]["fcp"]["value"]
  3554. )
  3555. assert response.data["data"][0]["count_unique_measurements_fcp"] == 1
  3556. assert (
  3557. response.data["data"][0]["min_measurements_fcp"]
  3558. == self.transaction_data["measurements"]["fcp"]["value"]
  3559. )
  3560. assert (
  3561. response.data["data"][0]["max_measurements_fcp"]
  3562. == self.transaction_data["measurements"]["fcp"]["value"]
  3563. )
  3564. assert (
  3565. response.data["data"][0]["avg_measurements_fcp"]
  3566. == self.transaction_data["measurements"]["fcp"]["value"]
  3567. )
  3568. assert (
  3569. response.data["data"][0]["sum_measurements_fcp"]
  3570. == self.transaction_data["measurements"]["fcp"]["value"]
  3571. )
  3572. def get_measurement_condition_response(self, query_str, field):
  3573. query = {
  3574. "field": ["transaction", "count()"] + (field if field else []),
  3575. "query": query_str,
  3576. }
  3577. response = self.do_request(query)
  3578. assert response.status_code == 200, response.content
  3579. return response
  3580. def assert_measurement_condition_without_results(self, query_str, field=None):
  3581. response = self.get_measurement_condition_response(query_str, field)
  3582. assert len(response.data["data"]) == 0
  3583. def assert_measurement_condition_with_results(self, query_str, field=None):
  3584. response = self.get_measurement_condition_response(query_str, field)
  3585. assert len(response.data["data"]) == 1
  3586. assert response.data["data"][0]["transaction"] == self.transaction_data["metadata"]["title"]
  3587. assert response.data["data"][0]["count"] == 1
  3588. def test_measurements_conditions(self):
  3589. self.store_event(self.transaction_data, self.project.id)
  3590. fcp = self.transaction_data["measurements"]["fcp"]["value"]
  3591. # equality condition
  3592. # We use json dumps here to ensure precision when converting from float to str
  3593. # This is necessary because equality on floating point values need to be precise
  3594. self.assert_measurement_condition_with_results(f"measurements.fcp:{json.dumps(fcp)}")
  3595. # greater than condition
  3596. self.assert_measurement_condition_with_results(f"measurements.fcp:>{fcp - 1}")
  3597. self.assert_measurement_condition_without_results(f"measurements.fcp:>{fcp + 1}")
  3598. # less than condition
  3599. self.assert_measurement_condition_with_results(f"measurements.fcp:<{fcp + 1}")
  3600. self.assert_measurement_condition_without_results(f"measurements.fcp:<{fcp - 1}")
  3601. # has condition
  3602. self.assert_measurement_condition_with_results("has:measurements.fcp")
  3603. self.assert_measurement_condition_without_results("!has:measurements.fcp")
  3604. def test_measurements_aggregation_conditions(self):
  3605. self.store_event(self.transaction_data, self.project.id)
  3606. fcp = self.transaction_data["measurements"]["fcp"]["value"]
  3607. functions = [
  3608. "percentile(measurements.fcp, 0.5)",
  3609. "min(measurements.fcp)",
  3610. "max(measurements.fcp)",
  3611. "avg(measurements.fcp)",
  3612. "sum(measurements.fcp)",
  3613. ]
  3614. for function in functions:
  3615. self.assert_measurement_condition_with_results(
  3616. f"{function}:>{fcp - 1}", field=[function]
  3617. )
  3618. self.assert_measurement_condition_without_results(
  3619. f"{function}:>{fcp + 1}", field=[function]
  3620. )
  3621. self.assert_measurement_condition_with_results(
  3622. f"{function}:<{fcp + 1}", field=[function]
  3623. )
  3624. self.assert_measurement_condition_without_results(
  3625. f"{function}:<{fcp - 1}", field=[function]
  3626. )
  3627. count_unique = "count_unique(measurements.fcp)"
  3628. self.assert_measurement_condition_with_results(f"{count_unique}:1", field=[count_unique])
  3629. self.assert_measurement_condition_without_results(f"{count_unique}:0", field=[count_unique])
  3630. def test_compare_numeric_aggregate(self):
  3631. self.store_event(self.transaction_data, self.project.id)
  3632. query = {
  3633. "field": [
  3634. "p75(measurements.fcp)",
  3635. "compare_numeric_aggregate(p75_measurements_fcp,greater,0)",
  3636. ],
  3637. }
  3638. response = self.do_request(query)
  3639. assert response.status_code == 200, response.content
  3640. assert len(response.data["data"]) == 1
  3641. assert (
  3642. response.data["data"][0]["compare_numeric_aggregate_p75_measurements_fcp_greater_0"]
  3643. == 1
  3644. )
  3645. query = {
  3646. "field": ["p75()", "compare_numeric_aggregate(p75,equals,0)"],
  3647. }
  3648. response = self.do_request(query)
  3649. assert response.status_code == 200, response.content
  3650. assert len(response.data["data"]) == 1
  3651. assert response.data["data"][0]["compare_numeric_aggregate_p75_equals_0"] == 0
  3652. def test_no_team_key_transactions(self):
  3653. transactions = [
  3654. "/blah_transaction/",
  3655. "/foo_transaction/",
  3656. "/zoo_transaction/",
  3657. ]
  3658. for transaction in transactions:
  3659. self.transaction_data["transaction"] = transaction
  3660. self.store_event(self.transaction_data, self.project.id)
  3661. query = {
  3662. "team": "myteams",
  3663. "project": [self.project.id],
  3664. # use the order by to ensure the result order
  3665. "orderby": "transaction",
  3666. "field": [
  3667. "team_key_transaction",
  3668. "transaction",
  3669. "transaction.status",
  3670. "project",
  3671. "epm()",
  3672. "failure_rate()",
  3673. "percentile(transaction.duration, 0.95)",
  3674. ],
  3675. }
  3676. response = self.do_request(query)
  3677. assert response.status_code == 200, response.content
  3678. data = response.data["data"]
  3679. assert len(data) == 3
  3680. assert data[0]["team_key_transaction"] == 0
  3681. assert data[0]["transaction"] == "/blah_transaction/"
  3682. assert data[1]["team_key_transaction"] == 0
  3683. assert data[1]["transaction"] == "/foo_transaction/"
  3684. assert data[2]["team_key_transaction"] == 0
  3685. assert data[2]["transaction"] == "/zoo_transaction/"
  3686. def test_team_key_transactions_my_teams(self):
  3687. team1 = self.create_team(organization=self.organization, name="Team A")
  3688. self.create_team_membership(team1, user=self.user)
  3689. self.project.add_team(team1)
  3690. team2 = self.create_team(organization=self.organization, name="Team B")
  3691. self.project.add_team(team2)
  3692. transactions = ["/blah_transaction/"]
  3693. key_transactions = [
  3694. (team1, "/foo_transaction/"),
  3695. (team2, "/zoo_transaction/"),
  3696. ]
  3697. for transaction in transactions:
  3698. self.transaction_data["transaction"] = transaction
  3699. self.store_event(self.transaction_data, self.project.id)
  3700. for team, transaction in key_transactions:
  3701. self.transaction_data["transaction"] = transaction
  3702. self.store_event(self.transaction_data, self.project.id)
  3703. TeamKeyTransaction.objects.create(
  3704. organization=self.organization,
  3705. transaction=transaction,
  3706. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  3707. )
  3708. query = {
  3709. "team": "myteams",
  3710. "project": [self.project.id],
  3711. "field": [
  3712. "team_key_transaction",
  3713. "transaction",
  3714. "transaction.status",
  3715. "project",
  3716. "epm()",
  3717. "failure_rate()",
  3718. "percentile(transaction.duration, 0.95)",
  3719. ],
  3720. }
  3721. query["orderby"] = ["team_key_transaction", "transaction"]
  3722. response = self.do_request(query)
  3723. assert response.status_code == 200, response.content
  3724. data = response.data["data"]
  3725. assert len(data) == 3
  3726. assert data[0]["team_key_transaction"] == 0
  3727. assert data[0]["transaction"] == "/blah_transaction/"
  3728. assert data[1]["team_key_transaction"] == 0
  3729. assert data[1]["transaction"] == "/zoo_transaction/"
  3730. assert data[2]["team_key_transaction"] == 1
  3731. assert data[2]["transaction"] == "/foo_transaction/"
  3732. # not specifying any teams should use my teams
  3733. query = {
  3734. "project": [self.project.id],
  3735. "field": [
  3736. "team_key_transaction",
  3737. "transaction",
  3738. "transaction.status",
  3739. "project",
  3740. "epm()",
  3741. "failure_rate()",
  3742. "percentile(transaction.duration, 0.95)",
  3743. ],
  3744. }
  3745. query["orderby"] = ["team_key_transaction", "transaction"]
  3746. response = self.do_request(query)
  3747. assert response.status_code == 200, response.content
  3748. data = response.data["data"]
  3749. assert len(data) == 3
  3750. assert data[0]["team_key_transaction"] == 0
  3751. assert data[0]["transaction"] == "/blah_transaction/"
  3752. assert data[1]["team_key_transaction"] == 0
  3753. assert data[1]["transaction"] == "/zoo_transaction/"
  3754. assert data[2]["team_key_transaction"] == 1
  3755. assert data[2]["transaction"] == "/foo_transaction/"
  3756. def test_team_key_transactions_orderby(self):
  3757. team1 = self.create_team(organization=self.organization, name="Team A")
  3758. team2 = self.create_team(organization=self.organization, name="Team B")
  3759. transactions = ["/blah_transaction/"]
  3760. key_transactions = [
  3761. (team1, "/foo_transaction/"),
  3762. (team2, "/zoo_transaction/"),
  3763. ]
  3764. for transaction in transactions:
  3765. self.transaction_data["transaction"] = transaction
  3766. self.store_event(self.transaction_data, self.project.id)
  3767. for team, transaction in key_transactions:
  3768. self.create_team_membership(team, user=self.user)
  3769. self.project.add_team(team)
  3770. self.transaction_data["transaction"] = transaction
  3771. self.store_event(self.transaction_data, self.project.id)
  3772. TeamKeyTransaction.objects.create(
  3773. organization=self.organization,
  3774. transaction=transaction,
  3775. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  3776. )
  3777. query = {
  3778. "team": "myteams",
  3779. "project": [self.project.id],
  3780. "field": [
  3781. "team_key_transaction",
  3782. "transaction",
  3783. "transaction.status",
  3784. "project",
  3785. "epm()",
  3786. "failure_rate()",
  3787. "percentile(transaction.duration, 0.95)",
  3788. ],
  3789. }
  3790. # test ascending order
  3791. query["orderby"] = ["team_key_transaction", "transaction"]
  3792. response = self.do_request(query)
  3793. assert response.status_code == 200, response.content
  3794. data = response.data["data"]
  3795. assert len(data) == 3
  3796. assert data[0]["team_key_transaction"] == 0
  3797. assert data[0]["transaction"] == "/blah_transaction/"
  3798. assert data[1]["team_key_transaction"] == 1
  3799. assert data[1]["transaction"] == "/foo_transaction/"
  3800. assert data[2]["team_key_transaction"] == 1
  3801. assert data[2]["transaction"] == "/zoo_transaction/"
  3802. # test descending order
  3803. query["orderby"] = ["-team_key_transaction", "-transaction"]
  3804. response = self.do_request(query)
  3805. assert response.status_code == 200, response.content
  3806. data = response.data["data"]
  3807. assert len(data) == 3
  3808. assert data[0]["team_key_transaction"] == 1
  3809. assert data[0]["transaction"] == "/zoo_transaction/"
  3810. assert data[1]["team_key_transaction"] == 1
  3811. assert data[1]["transaction"] == "/foo_transaction/"
  3812. assert data[2]["team_key_transaction"] == 0
  3813. assert data[2]["transaction"] == "/blah_transaction/"
  3814. def test_team_key_transactions_query(self):
  3815. team1 = self.create_team(organization=self.organization, name="Team A")
  3816. team2 = self.create_team(organization=self.organization, name="Team B")
  3817. transactions = ["/blah_transaction/"]
  3818. key_transactions = [
  3819. (team1, "/foo_transaction/"),
  3820. (team2, "/zoo_transaction/"),
  3821. ]
  3822. for transaction in transactions:
  3823. self.transaction_data["transaction"] = transaction
  3824. self.store_event(self.transaction_data, self.project.id)
  3825. for team, transaction in key_transactions:
  3826. self.create_team_membership(team, user=self.user)
  3827. self.project.add_team(team)
  3828. self.transaction_data["transaction"] = transaction
  3829. self.store_event(self.transaction_data, self.project.id)
  3830. TeamKeyTransaction.objects.create(
  3831. organization=self.organization,
  3832. project_team=ProjectTeam.objects.get(
  3833. project=self.project,
  3834. team=team,
  3835. ),
  3836. transaction=transaction,
  3837. )
  3838. query = {
  3839. "team": "myteams",
  3840. "project": [self.project.id],
  3841. # use the order by to ensure the result order
  3842. "orderby": "transaction",
  3843. "field": [
  3844. "team_key_transaction",
  3845. "transaction",
  3846. "transaction.status",
  3847. "project",
  3848. "epm()",
  3849. "failure_rate()",
  3850. "percentile(transaction.duration, 0.95)",
  3851. ],
  3852. }
  3853. # key transactions
  3854. query["query"] = "has:team_key_transaction"
  3855. response = self.do_request(query)
  3856. assert response.status_code == 200, response.content
  3857. data = response.data["data"]
  3858. assert len(data) == 2
  3859. assert data[0]["team_key_transaction"] == 1
  3860. assert data[0]["transaction"] == "/foo_transaction/"
  3861. assert data[1]["team_key_transaction"] == 1
  3862. assert data[1]["transaction"] == "/zoo_transaction/"
  3863. # key transactions
  3864. query["query"] = "team_key_transaction:true"
  3865. response = self.do_request(query)
  3866. assert response.status_code == 200, response.content
  3867. data = response.data["data"]
  3868. assert len(data) == 2
  3869. assert data[0]["team_key_transaction"] == 1
  3870. assert data[0]["transaction"] == "/foo_transaction/"
  3871. assert data[1]["team_key_transaction"] == 1
  3872. assert data[1]["transaction"] == "/zoo_transaction/"
  3873. # not key transactions
  3874. query["query"] = "!has:team_key_transaction"
  3875. response = self.do_request(query)
  3876. assert response.status_code == 200, response.content
  3877. data = response.data["data"]
  3878. assert len(data) == 1
  3879. assert data[0]["team_key_transaction"] == 0
  3880. assert data[0]["transaction"] == "/blah_transaction/"
  3881. # not key transactions
  3882. query["query"] = "team_key_transaction:false"
  3883. response = self.do_request(query)
  3884. assert response.status_code == 200, response.content
  3885. data = response.data["data"]
  3886. assert len(data) == 1
  3887. assert data[0]["team_key_transaction"] == 0
  3888. assert data[0]["transaction"] == "/blah_transaction/"
  3889. def test_too_many_team_key_transactions(self):
  3890. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  3891. with mock.patch(
  3892. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  3893. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  3894. ):
  3895. team = self.create_team(organization=self.organization, name="Team A")
  3896. self.create_team_membership(team, user=self.user)
  3897. self.project.add_team(team)
  3898. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  3899. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  3900. transaction = f"transaction-{team.id}-{i}"
  3901. self.transaction_data["transaction"] = transaction
  3902. self.store_event(self.transaction_data, self.project.id)
  3903. TeamKeyTransaction.objects.bulk_create(
  3904. [
  3905. TeamKeyTransaction(
  3906. organization=self.organization,
  3907. project_team=project_team,
  3908. transaction=f"transaction-{team.id}-{i}",
  3909. )
  3910. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  3911. ]
  3912. )
  3913. query = {
  3914. "team": "myteams",
  3915. "project": [self.project.id],
  3916. "orderby": "transaction",
  3917. "field": [
  3918. "team_key_transaction",
  3919. "transaction",
  3920. "transaction.status",
  3921. "project",
  3922. "epm()",
  3923. "failure_rate()",
  3924. "percentile(transaction.duration, 0.95)",
  3925. ],
  3926. }
  3927. response = self.do_request(query)
  3928. assert response.status_code == 200, response.content
  3929. data = response.data["data"]
  3930. assert len(data) == 2
  3931. assert (
  3932. sum(row["team_key_transaction"] for row in data)
  3933. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  3934. )
  3935. def test_no_pagination_param(self):
  3936. self.store_event(
  3937. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  3938. project_id=self.project.id,
  3939. )
  3940. query = {"field": ["id", "project.id"], "project": [self.project.id], "noPagination": True}
  3941. response = self.do_request(query)
  3942. assert response.status_code == 200
  3943. assert len(response.data["data"]) == 1
  3944. assert "Link" not in response
  3945. def test_nan_result(self):
  3946. query = {"field": ["apdex(300)"], "project": [self.project.id], "query": f"id:{'0' * 32}"}
  3947. response = self.do_request(query)
  3948. assert response.status_code == 200
  3949. assert len(response.data["data"]) == 1
  3950. assert response.data["data"][0]["apdex_300"] == 0
  3951. def test_equation_simple(self):
  3952. event_data = load_data("transaction", timestamp=before_now(minutes=1))
  3953. event_data["breakdowns"]["span_ops"]["ops.http"]["value"] = 1500
  3954. self.store_event(data=event_data, project_id=self.project.id)
  3955. query = {
  3956. "field": ["spans.http", "equation|spans.http / 3"],
  3957. "project": [self.project.id],
  3958. "query": "event.type:transaction",
  3959. }
  3960. response = self.do_request(
  3961. query,
  3962. {
  3963. "organizations:discover-basic": True,
  3964. },
  3965. )
  3966. assert response.status_code == 200
  3967. assert len(response.data["data"]) == 1
  3968. assert (
  3969. response.data["data"][0]["equation[0]"]
  3970. == event_data["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
  3971. )
  3972. def test_equation_sort(self):
  3973. event_data = load_data("transaction", timestamp=before_now(minutes=1))
  3974. event_data["breakdowns"]["span_ops"]["ops.http"]["value"] = 1500
  3975. self.store_event(data=event_data, project_id=self.project.id)
  3976. event_data2 = load_data("transaction", timestamp=before_now(minutes=1))
  3977. event_data2["breakdowns"]["span_ops"]["ops.http"]["value"] = 2000
  3978. self.store_event(data=event_data2, project_id=self.project.id)
  3979. query = {
  3980. "field": ["spans.http", "equation|spans.http / 3"],
  3981. "project": [self.project.id],
  3982. "orderby": "equation[0]",
  3983. "query": "event.type:transaction",
  3984. }
  3985. response = self.do_request(
  3986. query,
  3987. {
  3988. "organizations:discover-basic": True,
  3989. },
  3990. )
  3991. assert response.status_code == 200
  3992. assert len(response.data["data"]) == 2
  3993. assert (
  3994. response.data["data"][0]["equation[0]"]
  3995. == event_data["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
  3996. )
  3997. assert (
  3998. response.data["data"][1]["equation[0]"]
  3999. == event_data2["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
  4000. )
  4001. def test_equation_operation_limit(self):
  4002. query = {
  4003. "field": ["spans.http", f"equation|spans.http{' * 2' * 11}"],
  4004. "project": [self.project.id],
  4005. "query": "event.type:transaction",
  4006. }
  4007. response = self.do_request(
  4008. query,
  4009. {
  4010. "organizations:discover-basic": True,
  4011. },
  4012. )
  4013. assert response.status_code == 400
  4014. @mock.patch("sentry.api.bases.organization_events.MAX_FIELDS", 2)
  4015. def test_equation_field_limit(self):
  4016. query = {
  4017. "field": ["spans.http", "transaction.duration", "equation|5 * 2"],
  4018. "project": [self.project.id],
  4019. "query": "event.type:transaction",
  4020. }
  4021. response = self.do_request(
  4022. query,
  4023. {
  4024. "organizations:discover-basic": True,
  4025. },
  4026. )
  4027. assert response.status_code == 400
  4028. def test_count_if(self):
  4029. for i in range(5):
  4030. data = load_data(
  4031. "transaction",
  4032. timestamp=before_now(minutes=(1 + i)),
  4033. start_timestamp=before_now(minutes=(1 + i), milliseconds=100 if i < 3 else 200),
  4034. )
  4035. data["tags"] = {"sub_customer.is-Enterprise-42": "yes" if i == 0 else "no"}
  4036. self.store_event(data, project_id=self.project.id)
  4037. query = {
  4038. "field": [
  4039. "count_if(transaction.duration, less, 150)",
  4040. "count_if(transaction.duration, greater, 150)",
  4041. "count_if(sub_customer.is-Enterprise-42, equals, yes)",
  4042. "count_if(sub_customer.is-Enterprise-42, notEquals, yes)",
  4043. ],
  4044. "project": [self.project.id],
  4045. }
  4046. response = self.do_request(query)
  4047. assert response.status_code == 200
  4048. assert len(response.data["data"]) == 1
  4049. assert response.data["data"][0]["count_if_transaction_duration_less_150"] == 3
  4050. assert response.data["data"][0]["count_if_transaction_duration_greater_150"] == 2
  4051. assert response.data["data"][0]["count_if_sub_customer_is_Enterprise_42_equals_yes"] == 1
  4052. assert response.data["data"][0]["count_if_sub_customer_is_Enterprise_42_notEquals_yes"] == 4
  4053. def test_count_if_filter(self):
  4054. for i in range(5):
  4055. data = load_data(
  4056. "transaction",
  4057. timestamp=before_now(minutes=(1 + i)),
  4058. start_timestamp=before_now(minutes=(1 + i), milliseconds=100 if i < 3 else 200),
  4059. )
  4060. data["tags"] = {"sub_customer.is-Enterprise-42": "yes" if i == 0 else "no"}
  4061. self.store_event(data, project_id=self.project.id)
  4062. query = {
  4063. "field": [
  4064. "count_if(transaction.duration, less, 150)",
  4065. ],
  4066. "query": "count_if(transaction.duration, less, 150):>2",
  4067. "project": [self.project.id],
  4068. }
  4069. response = self.do_request(query)
  4070. assert response.status_code == 200
  4071. assert len(response.data["data"]) == 1
  4072. assert response.data["data"][0]["count_if_transaction_duration_less_150"] == 3
  4073. query = {
  4074. "field": [
  4075. "count_if(transaction.duration, less, 150)",
  4076. ],
  4077. "query": "count_if(transaction.duration, less, 150):<2",
  4078. "project": [self.project.id],
  4079. }
  4080. response = self.do_request(query)
  4081. assert response.status_code == 200
  4082. assert len(response.data["data"]) == 0
  4083. def test_filters_with_escaped_asterisk(self):
  4084. data = load_data("transaction", timestamp=before_now(minutes=1))
  4085. data["transaction"] = r"/:a*/:b-:c(\d\.\e+)"
  4086. self.store_event(data, project_id=self.project.id)
  4087. query = {
  4088. "field": ["transaction", "transaction.duration"],
  4089. # make sure to escape the asterisk so it's not treated as a wildcard
  4090. "query": r'transaction:"/:a\*/:b-:c(\d\.\e+)"',
  4091. "project": [self.project.id],
  4092. }
  4093. response = self.do_request(query)
  4094. assert response.status_code == 200
  4095. assert len(response.data["data"]) == 1
  4096. def test_filters_with_back_slashes(self):
  4097. data = load_data("transaction", timestamp=before_now(minutes=1))
  4098. data["transaction"] = r"a\b\c@d"
  4099. self.store_event(data, project_id=self.project.id)
  4100. query = {
  4101. "field": ["transaction", "transaction.duration"],
  4102. "query": r'transaction:"a\b\c@d"',
  4103. "project": [self.project.id],
  4104. }
  4105. response = self.do_request(query)
  4106. assert response.status_code == 200
  4107. assert len(response.data["data"]) == 1
  4108. def test_mobile_measurements(self):
  4109. data = load_data("transaction", timestamp=before_now(minutes=1))
  4110. data["measurements"]["frames_total"] = {"value": 100}
  4111. data["measurements"]["frames_slow"] = {"value": 10}
  4112. data["measurements"]["frames_frozen"] = {"value": 5}
  4113. data["measurements"]["stall_count"] = {"value": 2}
  4114. data["measurements"]["stall_total_time"] = {"value": 12}
  4115. data["measurements"]["stall_longest_time"] = {"value": 7}
  4116. self.store_event(data, project_id=self.project.id)
  4117. query = {
  4118. "field": [
  4119. "measurements.frames_total",
  4120. "measurements.frames_slow",
  4121. "measurements.frames_frozen",
  4122. "measurements.frames_slow_rate",
  4123. "measurements.frames_frozen_rate",
  4124. "measurements.stall_count",
  4125. "measurements.stall_total_time",
  4126. "measurements.stall_longest_time",
  4127. "measurements.stall_percentage",
  4128. ],
  4129. "query": "",
  4130. "project": [self.project.id],
  4131. }
  4132. response = self.do_request(query)
  4133. assert response.status_code == 200
  4134. data = response.data["data"]
  4135. assert len(data) == 1
  4136. assert data[0]["measurements.frames_total"] == 100
  4137. assert data[0]["measurements.frames_slow"] == 10
  4138. assert data[0]["measurements.frames_frozen"] == 5
  4139. assert data[0]["measurements.frames_slow_rate"] == 0.1
  4140. assert data[0]["measurements.frames_frozen_rate"] == 0.05
  4141. assert data[0]["measurements.stall_count"] == 2
  4142. assert data[0]["measurements.stall_total_time"] == 12
  4143. assert data[0]["measurements.stall_longest_time"] == 7
  4144. assert data[0]["measurements.stall_percentage"] == 0.004
  4145. meta = response.data["meta"]
  4146. assert meta["measurements.frames_total"] == "number"
  4147. assert meta["measurements.frames_slow"] == "number"
  4148. assert meta["measurements.frames_frozen"] == "number"
  4149. assert meta["measurements.frames_slow_rate"] == "percentage"
  4150. assert meta["measurements.frames_frozen_rate"] == "percentage"
  4151. assert meta["measurements.stall_count"] == "number"
  4152. assert meta["measurements.stall_total_time"] == "number"
  4153. assert meta["measurements.stall_longest_time"] == "number"
  4154. assert meta["measurements.stall_percentage"] == "percentage"
  4155. query = {
  4156. "field": [
  4157. "p75(measurements.frames_slow_rate)",
  4158. "p75(measurements.frames_frozen_rate)",
  4159. "percentile(measurements.frames_slow_rate,0.5)",
  4160. "percentile(measurements.frames_frozen_rate,0.5)",
  4161. "p75(measurements.stall_percentage)",
  4162. "percentile(measurements.stall_percentage,0.5)",
  4163. ],
  4164. "query": "",
  4165. "project": [self.project.id],
  4166. }
  4167. response = self.do_request(query)
  4168. assert response.status_code == 200
  4169. data = response.data["data"]
  4170. assert len(data) == 1
  4171. assert data[0]["p75_measurements_frames_slow_rate"] == 0.1
  4172. assert data[0]["p75_measurements_frames_frozen_rate"] == 0.05
  4173. assert data[0]["p75_measurements_stall_percentage"] == 0.004
  4174. assert data[0]["percentile_measurements_frames_slow_rate_0_5"] == 0.1
  4175. assert data[0]["percentile_measurements_frames_frozen_rate_0_5"] == 0.05
  4176. assert data[0]["percentile_measurements_stall_percentage_0_5"] == 0.004
  4177. meta = response.data["meta"]
  4178. assert meta["p75_measurements_frames_slow_rate"] == "percentage"
  4179. assert meta["p75_measurements_frames_frozen_rate"] == "percentage"
  4180. assert meta["p75_measurements_stall_percentage"] == "percentage"
  4181. assert meta["percentile_measurements_frames_slow_rate_0_5"] == "percentage"
  4182. assert meta["percentile_measurements_stall_percentage_0_5"] == "percentage"
  4183. def test_project_auto_fields(self):
  4184. project = self.create_project()
  4185. self.store_event(
  4186. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  4187. project_id=project.id,
  4188. )
  4189. query = {"field": ["environment"]}
  4190. response = self.do_request(query)
  4191. assert response.status_code == 200, response.content
  4192. assert len(response.data["data"]) == 1
  4193. assert response.data["data"][0]["environment"] == "staging"
  4194. assert response.data["data"][0]["project.name"] == project.slug
  4195. def test_timestamp_different_from_params(self):
  4196. project = self.create_project()
  4197. fifteen_days_ago = iso_format(before_now(days=15))
  4198. fifteen_days_later = iso_format(before_now(days=-15))
  4199. self.store_event(
  4200. data={
  4201. "event_id": "a" * 32,
  4202. "timestamp": iso_format(before_now(minutes=5)),
  4203. "fingerprint": ["1123581321"],
  4204. "user": {"email": "foo@example.com"},
  4205. "tags": {"language": "C++"},
  4206. },
  4207. project_id=project.id,
  4208. )
  4209. for query_text in [
  4210. f"timestamp:<{fifteen_days_ago}",
  4211. f"timestamp:<={fifteen_days_ago}",
  4212. f"timestamp:>{fifteen_days_later}",
  4213. f"timestamp:>={fifteen_days_later}",
  4214. ]:
  4215. query = {
  4216. "field": ["count()"],
  4217. "query": query_text,
  4218. "statsPeriod": "14d",
  4219. }
  4220. response = self.do_request(query)
  4221. assert response.status_code == 400, query_text
  4222. @mock.patch("sentry.search.events.builder.raw_snql_query")
  4223. def test_removes_unnecessary_default_project_and_transaction_thresholds(self, mock_snql_query):
  4224. mock_snql_query.side_effect = [{"meta": {}, "data": []}]
  4225. ProjectTransactionThreshold.objects.create(
  4226. project=self.project,
  4227. organization=self.organization,
  4228. # these are the default values that we use
  4229. threshold=constants.DEFAULT_PROJECT_THRESHOLD,
  4230. metric=TransactionMetric.DURATION.value,
  4231. )
  4232. ProjectTransactionThresholdOverride.objects.create(
  4233. transaction="transaction",
  4234. project=self.project,
  4235. organization=self.organization,
  4236. # these are the default values that we use
  4237. threshold=constants.DEFAULT_PROJECT_THRESHOLD,
  4238. metric=TransactionMetric.DURATION.value,
  4239. )
  4240. query = {
  4241. "field": ["apdex()", "user_misery()"],
  4242. "query": "event.type:transaction",
  4243. "project": [self.project.id],
  4244. }
  4245. response = self.do_request(
  4246. query,
  4247. features={
  4248. "organizations:discover-basic": True,
  4249. "organizations:global-views": True,
  4250. },
  4251. )
  4252. assert response.status_code == 200, response.content
  4253. assert mock_snql_query.call_count == 1
  4254. assert (
  4255. Function("tuple", ["duration", 300], "project_threshold_config")
  4256. in mock_snql_query.call_args_list[0][0][0].select
  4257. )
  4258. @mock.patch("sentry.search.events.builder.raw_snql_query")
  4259. def test_removes_unnecessary_default_project_and_transaction_thresholds_keeps_others(
  4260. self, mock_snql_query
  4261. ):
  4262. mock_snql_query.side_effect = [{"meta": {}, "data": []}]
  4263. ProjectTransactionThreshold.objects.create(
  4264. project=self.project,
  4265. organization=self.organization,
  4266. # these are the default values that we use
  4267. threshold=constants.DEFAULT_PROJECT_THRESHOLD,
  4268. metric=TransactionMetric.DURATION.value,
  4269. )
  4270. ProjectTransactionThresholdOverride.objects.create(
  4271. transaction="transaction",
  4272. project=self.project,
  4273. organization=self.organization,
  4274. # these are the default values that we use
  4275. threshold=constants.DEFAULT_PROJECT_THRESHOLD,
  4276. metric=TransactionMetric.DURATION.value,
  4277. )
  4278. project = self.create_project()
  4279. ProjectTransactionThreshold.objects.create(
  4280. project=project,
  4281. organization=self.organization,
  4282. threshold=100,
  4283. metric=TransactionMetric.LCP.value,
  4284. )
  4285. ProjectTransactionThresholdOverride.objects.create(
  4286. transaction="transaction",
  4287. project=project,
  4288. organization=self.organization,
  4289. threshold=200,
  4290. metric=TransactionMetric.LCP.value,
  4291. )
  4292. query = {
  4293. "field": ["apdex()", "user_misery()"],
  4294. "query": "event.type:transaction",
  4295. "project": [self.project.id, project.id],
  4296. }
  4297. response = self.do_request(
  4298. query,
  4299. features={
  4300. "organizations:discover-basic": True,
  4301. "organizations:global-views": True,
  4302. },
  4303. )
  4304. assert response.status_code == 200, response.content
  4305. assert mock_snql_query.call_count == 1
  4306. project_threshold_override_config_index = Function(
  4307. "indexOf",
  4308. [
  4309. # only 1 transaction override is present here
  4310. # because the other use to the default values
  4311. [(Function("toUInt64", [project.id]), "transaction")],
  4312. (Column("project_id"), Column("transaction")),
  4313. ],
  4314. "project_threshold_override_config_index",
  4315. )
  4316. project_threshold_config_index = Function(
  4317. "indexOf",
  4318. [
  4319. # only 1 project override is present here
  4320. # because the other use to the default values
  4321. [Function("toUInt64", [project.id])],
  4322. Column("project_id"),
  4323. ],
  4324. "project_threshold_config_index",
  4325. )
  4326. assert (
  4327. Function(
  4328. "if",
  4329. [
  4330. Function("equals", [project_threshold_override_config_index, 0]),
  4331. Function(
  4332. "if",
  4333. [
  4334. Function("equals", [project_threshold_config_index, 0]),
  4335. ("duration", 300),
  4336. Function(
  4337. "arrayElement", [[("lcp", 100)], project_threshold_config_index]
  4338. ),
  4339. ],
  4340. ),
  4341. Function(
  4342. "arrayElement",
  4343. [[("lcp", 200)], project_threshold_override_config_index],
  4344. ),
  4345. ],
  4346. "project_threshold_config",
  4347. )
  4348. in mock_snql_query.call_args_list[0][0][0].select
  4349. )
  4350. def test_count_web_vitals(self):
  4351. # Good
  4352. self.transaction_data["measurements"] = {
  4353. "lcp": {"value": constants.VITAL_THRESHOLDS["lcp"]["meh"] - 100},
  4354. }
  4355. self.store_event(self.transaction_data, self.project.id)
  4356. # Meh
  4357. self.transaction_data["measurements"] = {
  4358. "lcp": {"value": constants.VITAL_THRESHOLDS["lcp"]["meh"] + 100},
  4359. }
  4360. self.store_event(self.transaction_data, self.project.id)
  4361. self.store_event(self.transaction_data, self.project.id)
  4362. query = {
  4363. "field": [
  4364. "count_web_vitals(measurements.lcp, poor)",
  4365. "count_web_vitals(measurements.lcp, meh)",
  4366. "count_web_vitals(measurements.lcp, good)",
  4367. ]
  4368. }
  4369. response = self.do_request(query)
  4370. assert response.status_code == 200, response.content
  4371. assert len(response.data["data"]) == 1
  4372. assert response.data["data"][0] == {
  4373. "count_web_vitals_measurements_lcp_poor": 0,
  4374. "count_web_vitals_measurements_lcp_meh": 2,
  4375. "count_web_vitals_measurements_lcp_good": 1,
  4376. }
  4377. def test_count_web_vitals_invalid_vital(self):
  4378. query = {
  4379. "field": [
  4380. "count_web_vitals(measurements.foo, poor)",
  4381. ],
  4382. "project": [self.project.id],
  4383. }
  4384. response = self.do_request(query)
  4385. assert response.status_code == 400, response.content
  4386. query = {
  4387. "field": [
  4388. "count_web_vitals(tags[lcp], poor)",
  4389. ],
  4390. "project": [self.project.id],
  4391. }
  4392. response = self.do_request(query)
  4393. assert response.status_code == 400, response.content
  4394. query = {
  4395. "field": [
  4396. "count_web_vitals(transaction.duration, poor)",
  4397. ],
  4398. "project": [self.project.id],
  4399. }
  4400. response = self.do_request(query)
  4401. assert response.status_code == 400, response.content
  4402. query = {
  4403. "field": [
  4404. "count_web_vitals(measurements.lcp, bad)",
  4405. ],
  4406. "project": [self.project.id],
  4407. }
  4408. response = self.do_request(query)
  4409. assert response.status_code == 400, response.content
  4410. def test_tag_that_looks_like_aggregate(self):
  4411. data = load_data("transaction", timestamp=before_now(minutes=1))
  4412. data["tags"] = {"p95": "<5k"}
  4413. self.store_event(data, project_id=self.project.id)
  4414. query = {
  4415. "field": ["p95"],
  4416. "query": "p95:<5k",
  4417. "project": [self.project.id],
  4418. }
  4419. response = self.do_request(query)
  4420. assert response.status_code == 200, response.content
  4421. data = response.data["data"]
  4422. assert len(data) == 1
  4423. assert data[0]["p95"] == "<5k"
  4424. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  4425. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  4426. METRIC_STRINGS = [
  4427. "foo_transaction",
  4428. "bar_transaction",
  4429. "baz_transaction",
  4430. "staging",
  4431. "measurement_rating",
  4432. "good",
  4433. "meh",
  4434. ]
  4435. def setUp(self):
  4436. super().setUp()
  4437. self.min_ago = before_now(minutes=1)
  4438. self.two_min_ago = before_now(minutes=2)
  4439. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  4440. self.features = {
  4441. "organizations:performance-use-metrics": True,
  4442. }
  4443. def do_request(self, query, features=None):
  4444. if features is None:
  4445. features = {"organizations:discover-basic": True}
  4446. features.update(self.features)
  4447. self.login_as(user=self.user)
  4448. url = reverse(
  4449. "sentry-api-0-organization-eventsv2",
  4450. kwargs={"organization_slug": self.organization.slug},
  4451. )
  4452. with self.feature(features):
  4453. return self.client.get(url, query, format="json")
  4454. def test_no_projects(self):
  4455. response = self.do_request(
  4456. {
  4457. "dataset": "metricsEnhanced",
  4458. }
  4459. )
  4460. assert response.status_code == 200, response.content
  4461. def test_invalid_dataset(self):
  4462. response = self.do_request(
  4463. {
  4464. "dataset": "aFakeDataset",
  4465. "project": self.project.id,
  4466. }
  4467. )
  4468. assert response.status_code == 400, response.content
  4469. assert response.data["detail"] == "dataset must be one of: discover, metricsEnhanced"
  4470. def test_out_of_retention(self):
  4471. self.create_project()
  4472. with self.options({"system.event-retention-days": 10}):
  4473. query = {
  4474. "field": ["id", "timestamp"],
  4475. "orderby": ["-timestamp", "-id"],
  4476. "query": "event.type:transaction",
  4477. "start": iso_format(before_now(days=20)),
  4478. "end": iso_format(before_now(days=15)),
  4479. "dataset": "metricsEnhanced",
  4480. }
  4481. response = self.do_request(query)
  4482. assert response.status_code == 400, response.content
  4483. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  4484. def test_invalid_search_terms(self):
  4485. response = self.do_request(
  4486. {
  4487. "field": ["epm()"],
  4488. "query": "hi \n there",
  4489. "project": self.project.id,
  4490. "dataset": "metricsEnhanced",
  4491. }
  4492. )
  4493. assert response.status_code == 400, response.content
  4494. assert (
  4495. response.data["detail"]
  4496. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  4497. )
  4498. def test_project_name(self):
  4499. self.store_metric(
  4500. 1,
  4501. tags={"environment": "staging"},
  4502. timestamp=self.min_ago,
  4503. )
  4504. response = self.do_request(
  4505. {
  4506. "field": ["project.name", "environment", "epm()"],
  4507. "query": "event.type:transaction",
  4508. "dataset": "metricsEnhanced",
  4509. "per_page": 50,
  4510. }
  4511. )
  4512. assert response.status_code == 200, response.content
  4513. assert len(response.data["data"]) == 1
  4514. data = response.data["data"]
  4515. meta = response.data["meta"]
  4516. assert data[0]["project.name"] == self.project.slug
  4517. assert "project.id" not in data[0]
  4518. assert data[0]["environment"] == "staging"
  4519. assert meta["isMetricsData"]
  4520. assert meta["project.name"] == "string"
  4521. assert meta["environment"] == "string"
  4522. assert meta["epm"] == "number"
  4523. def test_title_alias(self):
  4524. """title is an alias to transaction name"""
  4525. self.store_metric(
  4526. 1,
  4527. tags={"transaction": "foo_transaction"},
  4528. timestamp=self.min_ago,
  4529. )
  4530. response = self.do_request(
  4531. {
  4532. "field": ["title", "p50()"],
  4533. "query": "event.type:transaction",
  4534. "dataset": "metricsEnhanced",
  4535. "per_page": 50,
  4536. }
  4537. )
  4538. assert response.status_code == 200, response.content
  4539. assert len(response.data["data"]) == 1
  4540. data = response.data["data"]
  4541. meta = response.data["meta"]
  4542. assert data[0]["title"] == "foo_transaction"
  4543. assert data[0]["p50"] == 1
  4544. assert meta["isMetricsData"]
  4545. assert meta["title"] == "string"
  4546. assert meta["p50"] == "duration"
  4547. def test_having_condition(self):
  4548. self.store_metric(
  4549. 1,
  4550. tags={"environment": "staging", "transaction": "foo_transaction"},
  4551. timestamp=self.min_ago,
  4552. )
  4553. self.store_metric(
  4554. # shouldn't show up
  4555. 100,
  4556. tags={"environment": "staging", "transaction": "bar_transaction"},
  4557. timestamp=self.min_ago,
  4558. )
  4559. response = self.do_request(
  4560. {
  4561. "field": ["transaction", "project", "p50(transaction.duration)"],
  4562. "query": "event.type:transaction p50(transaction.duration):<50",
  4563. "dataset": "metricsEnhanced",
  4564. "per_page": 50,
  4565. }
  4566. )
  4567. assert response.status_code == 200, response.content
  4568. assert len(response.data["data"]) == 1
  4569. data = response.data["data"]
  4570. meta = response.data["meta"]
  4571. assert data[0]["transaction"] == "foo_transaction"
  4572. assert data[0]["project"] == self.project.slug
  4573. assert data[0]["p50_transaction_duration"] == 1
  4574. assert meta["isMetricsData"]
  4575. assert meta["transaction"] == "string"
  4576. assert meta["project"] == "string"
  4577. assert meta["p50_transaction_duration"] == "duration"
  4578. def test_having_condition_with_preventing_aggregates(self):
  4579. self.store_metric(
  4580. 1,
  4581. tags={"environment": "staging", "transaction": "foo_transaction"},
  4582. timestamp=self.min_ago,
  4583. )
  4584. self.store_metric(
  4585. 100,
  4586. tags={"environment": "staging", "transaction": "bar_transaction"},
  4587. timestamp=self.min_ago,
  4588. )
  4589. response = self.do_request(
  4590. {
  4591. "field": ["transaction", "project", "p50(transaction.duration)"],
  4592. "query": "event.type:transaction p50(transaction.duration):<50",
  4593. "dataset": "metricsEnhanced",
  4594. "preventMetricAggregates": "1",
  4595. "per_page": 50,
  4596. }
  4597. )
  4598. assert response.status_code == 200, response.content
  4599. assert len(response.data["data"]) == 0
  4600. meta = response.data["meta"]
  4601. assert not meta["isMetricsData"]
  4602. assert meta["transaction"] == "string"
  4603. assert meta["project"] == "string"
  4604. assert meta["p50_transaction_duration"] == "duration"
  4605. def test_having_condition_not_selected(self):
  4606. self.store_metric(
  4607. 1,
  4608. tags={"environment": "staging", "transaction": "foo_transaction"},
  4609. timestamp=self.min_ago,
  4610. )
  4611. self.store_metric(
  4612. # shouldn't show up
  4613. 100,
  4614. tags={"environment": "staging", "transaction": "bar_transaction"},
  4615. timestamp=self.min_ago,
  4616. )
  4617. response = self.do_request(
  4618. {
  4619. "field": ["transaction", "project", "p50(transaction.duration)"],
  4620. "query": "event.type:transaction p75(transaction.duration):<50",
  4621. "dataset": "metricsEnhanced",
  4622. "per_page": 50,
  4623. }
  4624. )
  4625. assert response.status_code == 200, response.content
  4626. assert len(response.data["data"]) == 1
  4627. data = response.data["data"]
  4628. meta = response.data["meta"]
  4629. assert data[0]["transaction"] == "foo_transaction"
  4630. assert data[0]["project"] == self.project.slug
  4631. assert data[0]["p50_transaction_duration"] == 1
  4632. assert meta["isMetricsData"]
  4633. assert meta["transaction"] == "string"
  4634. assert meta["project"] == "string"
  4635. assert meta["p50_transaction_duration"] == "duration"
  4636. def test_non_metrics_tag_with_implicit_format(self):
  4637. self.store_metric(
  4638. 1,
  4639. tags={"environment": "staging", "transaction": "foo_transaction"},
  4640. timestamp=self.min_ago,
  4641. )
  4642. response = self.do_request(
  4643. {
  4644. "field": ["test", "p50(transaction.duration)"],
  4645. "query": "event.type:transaction",
  4646. "dataset": "metricsEnhanced",
  4647. "per_page": 50,
  4648. }
  4649. )
  4650. assert response.status_code == 200, response.content
  4651. assert len(response.data["data"]) == 0
  4652. assert not response.data["meta"]["isMetricsData"]
  4653. def test_performance_homepage_query(self):
  4654. self.store_metric(
  4655. 1,
  4656. tags={
  4657. "transaction": "foo_transaction",
  4658. "is_tolerated": "false",
  4659. "is_satisfied": "true",
  4660. },
  4661. timestamp=self.min_ago,
  4662. )
  4663. self.store_metric(
  4664. 1,
  4665. "measurements.fcp",
  4666. tags={"transaction": "foo_transaction"},
  4667. timestamp=self.min_ago,
  4668. )
  4669. self.store_metric(
  4670. 2,
  4671. "measurements.lcp",
  4672. tags={"transaction": "foo_transaction"},
  4673. timestamp=self.min_ago,
  4674. )
  4675. self.store_metric(
  4676. 3,
  4677. "measurements.fid",
  4678. tags={"transaction": "foo_transaction"},
  4679. timestamp=self.min_ago,
  4680. )
  4681. self.store_metric(
  4682. 4,
  4683. "measurements.cls",
  4684. tags={"transaction": "foo_transaction"},
  4685. timestamp=self.min_ago,
  4686. )
  4687. self.store_metric(
  4688. 1,
  4689. "user",
  4690. tags={"transaction": "foo_transaction", "is_user_miserable": "true"},
  4691. timestamp=self.min_ago,
  4692. )
  4693. response = self.do_request(
  4694. {
  4695. "field": [
  4696. "transaction",
  4697. "project",
  4698. "tpm()",
  4699. "p75(measurements.fcp)",
  4700. "p75(measurements.lcp)",
  4701. "p75(measurements.fid)",
  4702. "p75(measurements.cls)",
  4703. "count_unique(user)",
  4704. "apdex()",
  4705. "count_miserable(user)",
  4706. "user_misery()",
  4707. ],
  4708. "query": "event.type:transaction",
  4709. "dataset": "metricsEnhanced",
  4710. "per_page": 50,
  4711. }
  4712. )
  4713. assert len(response.data["data"]) == 1
  4714. data = response.data["data"][0]
  4715. meta = response.data["meta"]
  4716. assert data["transaction"] == "foo_transaction"
  4717. assert data["project"] == self.project.slug
  4718. assert data["p75_measurements_fcp"] == 1.0
  4719. assert data["p75_measurements_lcp"] == 2.0
  4720. assert data["p75_measurements_fid"] == 3.0
  4721. assert data["p75_measurements_cls"] == 4.0
  4722. assert data["apdex"] == 1.0
  4723. assert data["count_miserable_user"] == 1.0
  4724. assert data["user_misery"] == 0.058
  4725. assert meta["isMetricsData"]
  4726. assert meta["transaction"] == "string"
  4727. assert meta["project"] == "string"
  4728. assert meta["p75_measurements_fcp"] == "duration"
  4729. assert meta["p75_measurements_lcp"] == "duration"
  4730. assert meta["p75_measurements_fid"] == "duration"
  4731. assert meta["p75_measurements_cls"] == "duration"
  4732. assert meta["apdex"] == "number"
  4733. assert meta["count_miserable_user"] == "integer"
  4734. assert meta["user_misery"] == "number"
  4735. def test_no_team_key_transactions(self):
  4736. self.store_metric(1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago)
  4737. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  4738. query = {
  4739. "team": "myteams",
  4740. "project": [self.project.id],
  4741. # TODO sort by transaction here once that's possible for order to match the same test without metrics
  4742. "orderby": "p95()",
  4743. "field": [
  4744. "team_key_transaction",
  4745. "transaction",
  4746. "transaction.status",
  4747. "project",
  4748. "epm()",
  4749. "failure_rate()",
  4750. "p95()",
  4751. ],
  4752. "per_page": 50,
  4753. "dataset": "metricsEnhanced",
  4754. }
  4755. response = self.do_request(query)
  4756. assert response.status_code == 200, response.content
  4757. assert len(response.data["data"]) == 2
  4758. data = response.data["data"]
  4759. meta = response.data["meta"]
  4760. assert data[0]["team_key_transaction"] == 0
  4761. assert data[0]["transaction"] == "foo_transaction"
  4762. assert data[1]["team_key_transaction"] == 0
  4763. assert data[1]["transaction"] == "bar_transaction"
  4764. assert meta["isMetricsData"]
  4765. assert meta["team_key_transaction"] == "boolean"
  4766. assert meta["transaction"] == "string"
  4767. def test_team_key_transactions_my_teams(self):
  4768. team1 = self.create_team(organization=self.organization, name="Team A")
  4769. self.create_team_membership(team1, user=self.user)
  4770. self.project.add_team(team1)
  4771. team2 = self.create_team(organization=self.organization, name="Team B")
  4772. self.project.add_team(team2)
  4773. key_transactions = [
  4774. (team1, "foo_transaction"),
  4775. (team2, "baz_transaction"),
  4776. ]
  4777. # Not a key transaction
  4778. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  4779. for team, transaction in key_transactions:
  4780. self.store_metric(1, tags={"transaction": transaction}, timestamp=self.min_ago)
  4781. TeamKeyTransaction.objects.create(
  4782. organization=self.organization,
  4783. transaction=transaction,
  4784. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  4785. )
  4786. query = {
  4787. "team": "myteams",
  4788. "project": [self.project.id],
  4789. "field": [
  4790. "team_key_transaction",
  4791. "transaction",
  4792. "transaction.status",
  4793. "project",
  4794. "epm()",
  4795. "failure_rate()",
  4796. "p95()",
  4797. ],
  4798. "per_page": 50,
  4799. "dataset": "metricsEnhanced",
  4800. }
  4801. query["orderby"] = ["team_key_transaction", "p95()"]
  4802. response = self.do_request(query)
  4803. assert response.status_code == 200, response.content
  4804. assert len(response.data["data"]) == 3
  4805. data = response.data["data"]
  4806. meta = response.data["meta"]
  4807. assert data[0]["team_key_transaction"] == 0
  4808. assert data[0]["transaction"] == "baz_transaction"
  4809. assert data[1]["team_key_transaction"] == 0
  4810. assert data[1]["transaction"] == "bar_transaction"
  4811. assert data[2]["team_key_transaction"] == 1
  4812. assert data[2]["transaction"] == "foo_transaction"
  4813. assert meta["isMetricsData"]
  4814. assert meta["team_key_transaction"] == "boolean"
  4815. assert meta["transaction"] == "string"
  4816. # not specifying any teams should use my teams
  4817. query = {
  4818. "project": [self.project.id],
  4819. "field": [
  4820. "team_key_transaction",
  4821. "transaction",
  4822. "transaction.status",
  4823. "project",
  4824. "epm()",
  4825. "failure_rate()",
  4826. "p95()",
  4827. ],
  4828. "per_page": 50,
  4829. "dataset": "metricsEnhanced",
  4830. }
  4831. query["orderby"] = ["team_key_transaction", "p95()"]
  4832. response = self.do_request(query)
  4833. assert response.status_code == 200, response.content
  4834. assert len(response.data["data"]) == 3
  4835. data = response.data["data"]
  4836. meta = response.data["meta"]
  4837. assert data[0]["team_key_transaction"] == 0
  4838. assert data[0]["transaction"] == "baz_transaction"
  4839. assert data[1]["team_key_transaction"] == 0
  4840. assert data[1]["transaction"] == "bar_transaction"
  4841. assert data[2]["team_key_transaction"] == 1
  4842. assert data[2]["transaction"] == "foo_transaction"
  4843. assert meta["isMetricsData"]
  4844. assert meta["team_key_transaction"] == "boolean"
  4845. assert meta["transaction"] == "string"
  4846. def test_team_key_transactions_orderby(self):
  4847. team1 = self.create_team(organization=self.organization, name="Team A")
  4848. team2 = self.create_team(organization=self.organization, name="Team B")
  4849. key_transactions = [
  4850. (team1, "foo_transaction", 1),
  4851. (team2, "baz_transaction", 100),
  4852. ]
  4853. # Not a key transaction
  4854. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  4855. for team, transaction, value in key_transactions:
  4856. self.store_metric(value, tags={"transaction": transaction}, timestamp=self.min_ago)
  4857. self.create_team_membership(team, user=self.user)
  4858. self.project.add_team(team)
  4859. TeamKeyTransaction.objects.create(
  4860. organization=self.organization,
  4861. transaction=transaction,
  4862. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  4863. )
  4864. query = {
  4865. "team": "myteams",
  4866. "project": [self.project.id],
  4867. "field": [
  4868. "team_key_transaction",
  4869. "transaction",
  4870. "transaction.status",
  4871. "project",
  4872. "epm()",
  4873. "failure_rate()",
  4874. "p95()",
  4875. ],
  4876. "per_page": 50,
  4877. "dataset": "metricsEnhanced",
  4878. }
  4879. # test ascending order
  4880. query["orderby"] = ["team_key_transaction", "p95()"]
  4881. response = self.do_request(query)
  4882. assert response.status_code == 200, response.content
  4883. assert len(response.data["data"]) == 3
  4884. data = response.data["data"]
  4885. meta = response.data["meta"]
  4886. assert data[0]["team_key_transaction"] == 0
  4887. assert data[0]["transaction"] == "bar_transaction"
  4888. assert data[1]["team_key_transaction"] == 1
  4889. assert data[1]["transaction"] == "foo_transaction"
  4890. assert data[2]["team_key_transaction"] == 1
  4891. assert data[2]["transaction"] == "baz_transaction"
  4892. assert meta["isMetricsData"]
  4893. assert meta["team_key_transaction"] == "boolean"
  4894. assert meta["transaction"] == "string"
  4895. # test descending order
  4896. query["orderby"] = ["-team_key_transaction", "p95()"]
  4897. response = self.do_request(query)
  4898. assert response.status_code == 200, response.content
  4899. assert len(response.data["data"]) == 3
  4900. data = response.data["data"]
  4901. meta = response.data["meta"]
  4902. assert data[0]["team_key_transaction"] == 1
  4903. assert data[0]["transaction"] == "foo_transaction"
  4904. assert data[1]["team_key_transaction"] == 1
  4905. assert data[1]["transaction"] == "baz_transaction"
  4906. assert data[2]["team_key_transaction"] == 0
  4907. assert data[2]["transaction"] == "bar_transaction"
  4908. assert meta["isMetricsData"]
  4909. assert meta["team_key_transaction"] == "boolean"
  4910. assert meta["transaction"] == "string"
  4911. def test_team_key_transactions_query(self):
  4912. team1 = self.create_team(organization=self.organization, name="Team A")
  4913. team2 = self.create_team(organization=self.organization, name="Team B")
  4914. key_transactions = [
  4915. (team1, "foo_transaction", 1),
  4916. (team2, "baz_transaction", 100),
  4917. ]
  4918. # Not a key transaction
  4919. self.store_metric(100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago)
  4920. for team, transaction, value in key_transactions:
  4921. self.store_metric(value, tags={"transaction": transaction}, timestamp=self.min_ago)
  4922. self.create_team_membership(team, user=self.user)
  4923. self.project.add_team(team)
  4924. TeamKeyTransaction.objects.create(
  4925. organization=self.organization,
  4926. transaction=transaction,
  4927. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  4928. )
  4929. query = {
  4930. "team": "myteams",
  4931. "project": [self.project.id],
  4932. # use the order by to ensure the result order
  4933. "orderby": "p95()",
  4934. "field": [
  4935. "team_key_transaction",
  4936. "transaction",
  4937. "transaction.status",
  4938. "project",
  4939. "epm()",
  4940. "failure_rate()",
  4941. "p95()",
  4942. ],
  4943. "per_page": 50,
  4944. "dataset": "metricsEnhanced",
  4945. }
  4946. # key transactions
  4947. query["query"] = "has:team_key_transaction"
  4948. response = self.do_request(query)
  4949. assert response.status_code == 200, response.content
  4950. assert len(response.data["data"]) == 2
  4951. data = response.data["data"]
  4952. meta = response.data["meta"]
  4953. assert data[0]["team_key_transaction"] == 1
  4954. assert data[0]["transaction"] == "foo_transaction"
  4955. assert data[1]["team_key_transaction"] == 1
  4956. assert data[1]["transaction"] == "baz_transaction"
  4957. assert meta["isMetricsData"]
  4958. assert meta["team_key_transaction"] == "boolean"
  4959. assert meta["transaction"] == "string"
  4960. # key transactions
  4961. query["query"] = "team_key_transaction:true"
  4962. response = self.do_request(query)
  4963. assert response.status_code == 200, response.content
  4964. assert len(response.data["data"]) == 2
  4965. data = response.data["data"]
  4966. meta = response.data["meta"]
  4967. assert data[0]["team_key_transaction"] == 1
  4968. assert data[0]["transaction"] == "foo_transaction"
  4969. assert data[1]["team_key_transaction"] == 1
  4970. assert data[1]["transaction"] == "baz_transaction"
  4971. assert meta["isMetricsData"]
  4972. assert meta["team_key_transaction"] == "boolean"
  4973. assert meta["transaction"] == "string"
  4974. # not key transactions
  4975. query["query"] = "!has:team_key_transaction"
  4976. response = self.do_request(query)
  4977. assert response.status_code == 200, response.content
  4978. assert len(response.data["data"]) == 1
  4979. data = response.data["data"]
  4980. meta = response.data["meta"]
  4981. assert data[0]["team_key_transaction"] == 0
  4982. assert data[0]["transaction"] == "bar_transaction"
  4983. assert meta["isMetricsData"]
  4984. assert meta["team_key_transaction"] == "boolean"
  4985. assert meta["transaction"] == "string"
  4986. # not key transactions
  4987. query["query"] = "team_key_transaction:false"
  4988. response = self.do_request(query)
  4989. assert response.status_code == 200, response.content
  4990. assert len(response.data["data"]) == 1
  4991. data = response.data["data"]
  4992. meta = response.data["meta"]
  4993. assert data[0]["team_key_transaction"] == 0
  4994. assert data[0]["transaction"] == "bar_transaction"
  4995. assert meta["isMetricsData"]
  4996. assert meta["team_key_transaction"] == "boolean"
  4997. assert meta["transaction"] == "string"
  4998. def test_too_many_team_key_transactions(self):
  4999. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  5000. with mock.patch(
  5001. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  5002. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  5003. ):
  5004. team = self.create_team(organization=self.organization, name="Team A")
  5005. self.create_team_membership(team, user=self.user)
  5006. self.project.add_team(team)
  5007. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  5008. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  5009. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  5010. self.store_metric(
  5011. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  5012. )
  5013. TeamKeyTransaction.objects.bulk_create(
  5014. [
  5015. TeamKeyTransaction(
  5016. organization=self.organization,
  5017. project_team=project_team,
  5018. transaction=transactions[i],
  5019. )
  5020. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  5021. ]
  5022. )
  5023. query = {
  5024. "team": "myteams",
  5025. "project": [self.project.id],
  5026. "orderby": "p95()",
  5027. "field": [
  5028. "team_key_transaction",
  5029. "transaction",
  5030. "transaction.status",
  5031. "project",
  5032. "epm()",
  5033. "failure_rate()",
  5034. "p95()",
  5035. ],
  5036. "dataset": "metricsEnhanced",
  5037. "per_page": 50,
  5038. }
  5039. response = self.do_request(query)
  5040. assert response.status_code == 200, response.content
  5041. assert len(response.data["data"]) == 2
  5042. data = response.data["data"]
  5043. meta = response.data["meta"]
  5044. assert (
  5045. sum(row["team_key_transaction"] for row in data)
  5046. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  5047. )
  5048. assert meta["isMetricsData"]
  5049. def test_measurement_rating(self):
  5050. self.store_metric(
  5051. 50,
  5052. metric="measurements.lcp",
  5053. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  5054. timestamp=self.min_ago,
  5055. )
  5056. self.store_metric(
  5057. 15,
  5058. metric="measurements.fp",
  5059. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  5060. timestamp=self.min_ago,
  5061. )
  5062. self.store_metric(
  5063. 1500,
  5064. metric="measurements.fcp",
  5065. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  5066. timestamp=self.min_ago,
  5067. )
  5068. self.store_metric(
  5069. 125,
  5070. metric="measurements.fid",
  5071. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  5072. timestamp=self.min_ago,
  5073. )
  5074. self.store_metric(
  5075. 0.15,
  5076. metric="measurements.cls",
  5077. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  5078. timestamp=self.min_ago,
  5079. )
  5080. response = self.do_request(
  5081. {
  5082. "field": [
  5083. "transaction",
  5084. "count_web_vitals(measurements.lcp, good)",
  5085. "count_web_vitals(measurements.fp, good)",
  5086. "count_web_vitals(measurements.fcp, meh)",
  5087. "count_web_vitals(measurements.fid, meh)",
  5088. "count_web_vitals(measurements.cls, good)",
  5089. ],
  5090. "query": "event.type:transaction",
  5091. "dataset": "metricsEnhanced",
  5092. "per_page": 50,
  5093. }
  5094. )
  5095. assert response.status_code == 200, response.content
  5096. assert len(response.data["data"]) == 1
  5097. data = response.data["data"]
  5098. meta = response.data["meta"]
  5099. assert data[0]["count_web_vitals_measurements_lcp_good"] == 1
  5100. assert data[0]["count_web_vitals_measurements_fp_good"] == 1
  5101. assert data[0]["count_web_vitals_measurements_fcp_meh"] == 1
  5102. assert data[0]["count_web_vitals_measurements_fid_meh"] == 1
  5103. assert data[0]["count_web_vitals_measurements_cls_good"] == 1
  5104. assert meta["isMetricsData"]
  5105. assert meta["count_web_vitals_measurements_lcp_good"] == "integer"
  5106. assert meta["count_web_vitals_measurements_fp_good"] == "integer"
  5107. assert meta["count_web_vitals_measurements_fcp_meh"] == "integer"
  5108. assert meta["count_web_vitals_measurements_fid_meh"] == "integer"
  5109. assert meta["count_web_vitals_measurements_cls_good"] == "integer"
  5110. def test_measurement_rating_that_does_not_exist(self):
  5111. self.store_metric(
  5112. 1,
  5113. metric="measurements.lcp",
  5114. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  5115. timestamp=self.min_ago,
  5116. )
  5117. response = self.do_request(
  5118. {
  5119. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  5120. "query": "event.type:transaction",
  5121. "dataset": "metricsEnhanced",
  5122. "per_page": 50,
  5123. }
  5124. )
  5125. assert response.status_code == 200, response.content
  5126. assert len(response.data["data"]) == 1
  5127. data = response.data["data"]
  5128. meta = response.data["meta"]
  5129. assert data[0]["count_web_vitals_measurements_lcp_poor"] == 0
  5130. assert meta["isMetricsData"]
  5131. assert meta["count_web_vitals_measurements_lcp_poor"] == "integer"
  5132. def test_count_web_vitals_invalid_vital(self):
  5133. query = {
  5134. "field": [
  5135. "count_web_vitals(measurements.foo, poor)",
  5136. ],
  5137. "project": [self.project.id],
  5138. "dataset": "metricsEnhanced",
  5139. }
  5140. response = self.do_request(query)
  5141. assert response.status_code == 400, response.content
  5142. query = {
  5143. "field": [
  5144. "count_web_vitals(tags[lcp], poor)",
  5145. ],
  5146. "project": [self.project.id],
  5147. "dataset": "metricsEnhanced",
  5148. }
  5149. response = self.do_request(query)
  5150. assert response.status_code == 400, response.content
  5151. query = {
  5152. "field": [
  5153. "count_web_vitals(transaction.duration, poor)",
  5154. ],
  5155. "project": [self.project.id],
  5156. "dataset": "metricsEnhanced",
  5157. }
  5158. response = self.do_request(query)
  5159. assert response.status_code == 400, response.content
  5160. query = {
  5161. "field": [
  5162. "count_web_vitals(measurements.lcp, bad)",
  5163. ],
  5164. "project": [self.project.id],
  5165. "dataset": "metricsEnhanced",
  5166. }
  5167. response = self.do_request(query)
  5168. assert response.status_code == 400, response.content
  5169. @mock.patch("sentry.snuba.metrics_enhanced_performance.MetricsQueryBuilder")
  5170. def test_failed_dry_run_does_not_error(self, mock_builder):
  5171. with self.feature("organizations:performance-dry-run-mep"):
  5172. mock_builder.side_effect = InvalidSearchQuery("Something bad")
  5173. query = {
  5174. "field": ["count()"],
  5175. "project": [self.project.id],
  5176. }
  5177. response = self.do_request(query)
  5178. assert response.status_code == 200, response.content
  5179. assert len(mock_builder.mock_calls) == 1
  5180. assert mock_builder.call_args.kwargs["dry_run"]
  5181. mock_builder.side_effect = IncompatibleMetricsQuery("Something bad")
  5182. query = {
  5183. "field": ["count()"],
  5184. "project": [self.project.id],
  5185. }
  5186. response = self.do_request(query)
  5187. assert response.status_code == 200, response.content
  5188. assert len(mock_builder.mock_calls) == 2
  5189. assert mock_builder.call_args.kwargs["dry_run"]
  5190. mock_builder.side_effect = InvalidConditionError("Something bad")
  5191. query = {
  5192. "field": ["count()"],
  5193. "project": [self.project.id],
  5194. }
  5195. response = self.do_request(query)
  5196. assert response.status_code == 200, response.content
  5197. assert len(mock_builder.mock_calls) == 3
  5198. assert mock_builder.call_args.kwargs["dry_run"]