test_organization_events.py 228 KB

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