test_organization_events.py 240 KB

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