test_organization_events.py 251 KB

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