test_organization_events.py 244 KB

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