test_organization_events.py 241 KB

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