test_organization_events.py 243 KB

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