test_organization_events_v2.py 213 KB

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