test_organization_events_v2.py 169 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454
  1. from base64 import b64encode
  2. import pytest
  3. from django.urls import reverse
  4. from pytz import utc
  5. from sentry.discover.models import KeyTransaction, TeamKeyTransaction
  6. from sentry.models import ApiKey, ProjectTeam, ProjectTransactionThreshold
  7. from sentry.models.transaction_threshold import (
  8. ProjectTransactionThresholdOverride,
  9. TransactionMetric,
  10. )
  11. from sentry.search.events.constants import SEMVER_ALIAS, SEMVER_PACKAGE_ALIAS
  12. from sentry.testutils import APITestCase, SnubaTestCase
  13. from sentry.testutils.helpers import parse_link_header
  14. from sentry.testutils.helpers.datetime import before_now, iso_format
  15. from sentry.utils import json
  16. from sentry.utils.compat import mock, zip
  17. from sentry.utils.samples import load_data
  18. from sentry.utils.snuba import QueryExecutionError, QueryIllegalTypeOfArgument, RateLimitExceeded
  19. MAX_QUERYABLE_TRANSACTION_THRESHOLDS = 1
  20. class OrganizationEventsV2EndpointTest(APITestCase, SnubaTestCase):
  21. def setUp(self):
  22. super().setUp()
  23. self.min_ago = iso_format(before_now(minutes=1))
  24. self.two_min_ago = iso_format(before_now(minutes=2))
  25. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  26. def do_request(self, query, features=None):
  27. if features is None:
  28. features = {"organizations:discover-basic": True}
  29. self.login_as(user=self.user)
  30. url = reverse(
  31. "sentry-api-0-organization-eventsv2",
  32. kwargs={"organization_slug": self.organization.slug},
  33. )
  34. with self.feature(features):
  35. return self.client.get(url, query, format="json")
  36. def test_no_projects(self):
  37. response = self.do_request({})
  38. assert response.status_code == 200, response.content
  39. assert len(response.data) == 0
  40. def test_api_key_request(self):
  41. project = self.create_project()
  42. self.store_event(
  43. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  44. project_id=project.id,
  45. )
  46. # Project ID cannot be inffered when using an org API key, so that must
  47. # be passed in the parameters
  48. api_key = ApiKey.objects.create(organization=self.organization, scope_list=["org:read"])
  49. query = {"field": ["project.name", "environment"], "project": [project.id]}
  50. url = reverse(
  51. "sentry-api-0-organization-eventsv2",
  52. kwargs={"organization_slug": self.organization.slug},
  53. )
  54. response = self.client.get(
  55. url,
  56. query,
  57. format="json",
  58. HTTP_AUTHORIZATION=b"Basic " + b64encode(f"{api_key.key}:".encode("utf-8")),
  59. )
  60. assert response.status_code == 200, response.content
  61. assert len(response.data["data"]) == 1
  62. assert response.data["data"][0]["project.name"] == project.slug
  63. def test_performance_view_feature(self):
  64. self.store_event(
  65. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  66. project_id=self.project.id,
  67. )
  68. query = {"field": ["id", "project.id"], "project": [self.project.id]}
  69. response = self.do_request(query)
  70. assert response.status_code == 200
  71. assert len(response.data["data"]) == 1
  72. def test_multi_project_feature_gate_rejection(self):
  73. team = self.create_team(organization=self.organization, members=[self.user])
  74. project = self.create_project(organization=self.organization, teams=[team])
  75. project2 = self.create_project(organization=self.organization, teams=[team])
  76. self.store_event(
  77. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  78. project_id=project.id,
  79. )
  80. self.store_event(
  81. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  82. project_id=project2.id,
  83. )
  84. query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
  85. response = self.do_request(query)
  86. assert response.status_code == 400
  87. assert "events from multiple projects" in response.data["detail"]
  88. def test_invalid_search_terms(self):
  89. project = self.create_project()
  90. self.store_event(
  91. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  92. project_id=project.id,
  93. )
  94. query = {"field": ["id"], "query": "hi \n there"}
  95. response = self.do_request(query)
  96. assert response.status_code == 400, response.content
  97. assert (
  98. response.data["detail"]
  99. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  100. )
  101. @mock.patch("sentry.snuba.discover.raw_query")
  102. def test_handling_snuba_errors(self, mock_query):
  103. mock_query.side_effect = RateLimitExceeded("test")
  104. project = self.create_project()
  105. self.store_event(
  106. data={"event_id": "a" * 32, "message": "how to make fast"}, project_id=project.id
  107. )
  108. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  109. response = self.do_request(query)
  110. assert response.status_code == 400, response.content
  111. assert (
  112. response.data["detail"]
  113. == "Query timeout. Please try again. If the problem persists try a smaller date range or fewer projects."
  114. )
  115. mock_query.side_effect = QueryExecutionError("test")
  116. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  117. response = self.do_request(query)
  118. assert response.status_code == 500, response.content
  119. assert response.data["detail"] == "Internal error. Your query failed to run."
  120. mock_query.side_effect = QueryIllegalTypeOfArgument("test")
  121. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  122. response = self.do_request(query)
  123. assert response.status_code == 400, response.content
  124. assert response.data["detail"] == "Invalid query. Argument to function is wrong type."
  125. def test_out_of_retention(self):
  126. self.create_project()
  127. with self.options({"system.event-retention-days": 10}):
  128. query = {
  129. "field": ["id", "timestamp"],
  130. "orderby": ["-timestamp", "-id"],
  131. "start": iso_format(before_now(days=20)),
  132. "end": iso_format(before_now(days=15)),
  133. }
  134. response = self.do_request(query)
  135. assert response.status_code == 400, response.content
  136. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  137. def test_raw_data(self):
  138. project = self.create_project()
  139. self.store_event(
  140. data={
  141. "event_id": "a" * 32,
  142. "environment": "staging",
  143. "timestamp": self.two_min_ago,
  144. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  145. },
  146. project_id=project.id,
  147. )
  148. self.store_event(
  149. data={
  150. "event_id": "b" * 32,
  151. "environment": "staging",
  152. "timestamp": self.min_ago,
  153. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  154. },
  155. project_id=project.id,
  156. )
  157. query = {
  158. "field": ["id", "project.id", "user.email", "user.ip", "timestamp"],
  159. "orderby": "-timestamp",
  160. }
  161. response = self.do_request(query)
  162. assert response.status_code == 200, response.content
  163. data = response.data["data"]
  164. assert len(data) == 2
  165. assert data[0]["id"] == "b" * 32
  166. assert data[0]["project.id"] == project.id
  167. assert data[0]["user.email"] == "foo@example.com"
  168. assert "project.name" not in data[0], "project.id does not auto select name"
  169. assert "project" not in data[0]
  170. meta = response.data["meta"]
  171. assert meta["id"] == "string"
  172. assert meta["user.email"] == "string"
  173. assert meta["user.ip"] == "string"
  174. assert meta["timestamp"] == "date"
  175. def test_project_name(self):
  176. project = self.create_project()
  177. self.store_event(
  178. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  179. project_id=project.id,
  180. )
  181. query = {"field": ["project.name", "environment"]}
  182. response = self.do_request(query)
  183. assert response.status_code == 200, response.content
  184. assert len(response.data["data"]) == 1
  185. assert response.data["data"][0]["project.name"] == project.slug
  186. assert "project.id" not in response.data["data"][0]
  187. assert response.data["data"][0]["environment"] == "staging"
  188. def test_project_without_name(self):
  189. project = self.create_project()
  190. self.store_event(
  191. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  192. project_id=project.id,
  193. )
  194. query = {"field": ["project", "environment"]}
  195. response = self.do_request(query)
  196. assert response.status_code == 200, response.content
  197. assert len(response.data["data"]) == 1
  198. assert response.data["data"][0]["project"] == project.slug
  199. assert response.data["meta"]["project"] == "string"
  200. assert "project.id" not in response.data["data"][0]
  201. assert response.data["data"][0]["environment"] == "staging"
  202. def test_project_in_query(self):
  203. project = self.create_project()
  204. self.store_event(
  205. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  206. project_id=project.id,
  207. )
  208. query = {
  209. "field": ["project", "count()"],
  210. "query": 'project:"%s"' % project.slug,
  211. "statsPeriod": "14d",
  212. }
  213. response = self.do_request(query)
  214. assert response.status_code == 200, response.content
  215. assert len(response.data["data"]) == 1
  216. assert response.data["data"][0]["project"] == project.slug
  217. assert "project.id" not in response.data["data"][0]
  218. def test_project_in_query_not_in_header(self):
  219. project = self.create_project()
  220. other_project = self.create_project()
  221. self.store_event(
  222. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  223. project_id=project.id,
  224. )
  225. query = {
  226. "field": ["project", "count()"],
  227. "query": 'project:"%s"' % project.slug,
  228. "statsPeriod": "14d",
  229. "project": other_project.id,
  230. }
  231. response = self.do_request(query)
  232. assert response.status_code == 400, response.content
  233. assert (
  234. response.data["detail"]
  235. == f"Invalid query. Project(s) {project.slug} do not exist or are not actively selected."
  236. )
  237. def test_project_in_query_does_not_exist(self):
  238. project = self.create_project()
  239. self.store_event(
  240. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  241. project_id=project.id,
  242. )
  243. query = {"field": ["project", "count()"], "query": "project:morty", "statsPeriod": "14d"}
  244. response = self.do_request(query)
  245. assert response.status_code == 400, response.content
  246. assert (
  247. response.data["detail"]
  248. == "Invalid query. Project(s) morty do not exist or are not actively selected."
  249. )
  250. def test_not_project_in_query_but_in_header(self):
  251. team = self.create_team(organization=self.organization, members=[self.user])
  252. project = self.create_project(organization=self.organization, teams=[team])
  253. project2 = self.create_project(organization=self.organization, teams=[team])
  254. self.store_event(
  255. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  256. project_id=project.id,
  257. )
  258. self.store_event(
  259. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  260. project_id=project2.id,
  261. )
  262. query = {
  263. "field": ["id", "project.id"],
  264. "project": [project.id],
  265. "query": f"!project:{project2.slug}",
  266. }
  267. response = self.do_request(query)
  268. assert response.status_code == 200
  269. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  270. def test_not_project_in_query_with_all_projects(self):
  271. team = self.create_team(organization=self.organization, members=[self.user])
  272. project = self.create_project(organization=self.organization, teams=[team])
  273. project2 = self.create_project(organization=self.organization, teams=[team])
  274. self.store_event(
  275. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  276. project_id=project.id,
  277. )
  278. self.store_event(
  279. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  280. project_id=project2.id,
  281. )
  282. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  283. query = {
  284. "field": ["id", "project.id"],
  285. "project": [-1],
  286. "query": f"!project:{project2.slug}",
  287. }
  288. response = self.do_request(query, features=features)
  289. assert response.status_code == 200
  290. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  291. def test_project_condition_used_for_automatic_filters(self):
  292. project = self.create_project()
  293. self.store_event(
  294. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  295. project_id=project.id,
  296. )
  297. query = {
  298. "field": ["project", "count()"],
  299. "query": 'project:"%s"' % project.slug,
  300. "statsPeriod": "14d",
  301. }
  302. response = self.do_request(query)
  303. assert response.status_code == 200, response.content
  304. assert len(response.data["data"]) == 1
  305. assert response.data["data"][0]["project"] == project.slug
  306. assert "project.id" not in response.data["data"][0]
  307. def test_auto_insert_project_name_when_event_id_present(self):
  308. project = self.create_project()
  309. self.store_event(
  310. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  311. project_id=project.id,
  312. )
  313. query = {"field": ["id"], "statsPeriod": "1h"}
  314. response = self.do_request(query)
  315. assert response.status_code == 200, response.content
  316. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32}]
  317. def test_auto_insert_project_name_when_event_id_present_with_aggregate(self):
  318. project = self.create_project()
  319. self.store_event(
  320. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  321. project_id=project.id,
  322. )
  323. query = {"field": ["id", "count()"], "statsPeriod": "1h"}
  324. response = self.do_request(query)
  325. assert response.status_code == 200, response.content
  326. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32, "count": 1}]
  327. def test_user_search(self):
  328. project = self.create_project()
  329. data = load_data("transaction", timestamp=before_now(minutes=1))
  330. data["user"] = {
  331. "email": "foo@example.com",
  332. "id": "123",
  333. "ip_address": "127.0.0.1",
  334. "username": "foo",
  335. }
  336. self.store_event(data, project_id=project.id)
  337. fields = {
  338. "email": "user.email",
  339. "id": "user.id",
  340. "ip_address": "user.ip",
  341. "username": "user.username",
  342. }
  343. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  344. for key, value in data["user"].items():
  345. field = fields[key]
  346. query = {
  347. "field": ["project", "user"],
  348. "query": f"{field}:{value}",
  349. "statsPeriod": "14d",
  350. }
  351. response = self.do_request(query, features=features)
  352. assert response.status_code == 200, response.content
  353. assert len(response.data["data"]) == 1
  354. assert response.data["data"][0]["project"] == project.slug
  355. assert response.data["data"][0]["user"] == "id:123"
  356. def test_has_user(self):
  357. project = self.create_project()
  358. data = load_data("transaction", timestamp=before_now(minutes=1))
  359. self.store_event(data, project_id=project.id)
  360. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  361. for value in data["user"].values():
  362. query = {"field": ["project", "user"], "query": "has:user", "statsPeriod": "14d"}
  363. response = self.do_request(query, features=features)
  364. assert response.status_code == 200, response.content
  365. assert len(response.data["data"]) == 1
  366. assert response.data["data"][0]["user"] == "ip:{}".format(data["user"]["ip_address"])
  367. def test_has_issue(self):
  368. project = self.create_project()
  369. event = self.store_event(
  370. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  371. )
  372. data = load_data("transaction", timestamp=before_now(minutes=1))
  373. self.store_event(data, project_id=project.id)
  374. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  375. # should only show 1 event of type default
  376. query = {"field": ["project", "issue"], "query": "has:issue", "statsPeriod": "14d"}
  377. response = self.do_request(query, features=features)
  378. assert response.status_code == 200, response.content
  379. assert len(response.data["data"]) == 1
  380. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  381. # should only show 1 event of type default
  382. query = {
  383. "field": ["project", "issue"],
  384. "query": "event.type:default has:issue",
  385. "statsPeriod": "14d",
  386. }
  387. response = self.do_request(query, features=features)
  388. assert response.status_code == 200, response.content
  389. assert len(response.data["data"]) == 1
  390. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  391. # should show no results because no the default event has an issue
  392. query = {
  393. "field": ["project", "issue"],
  394. "query": "event.type:default !has:issue",
  395. "statsPeriod": "14d",
  396. }
  397. response = self.do_request(query, features=features)
  398. assert response.status_code == 200, response.content
  399. assert len(response.data["data"]) == 0
  400. # should show no results because no transactions have issues
  401. query = {
  402. "field": ["project", "issue"],
  403. "query": "event.type:transaction has:issue",
  404. "statsPeriod": "14d",
  405. }
  406. response = self.do_request(query, features=features)
  407. assert response.status_code == 200, response.content
  408. assert len(response.data["data"]) == 0
  409. # should only show 1 event of type transaction since they dont have issues
  410. query = {
  411. "field": ["project", "issue"],
  412. "query": "event.type:transaction !has:issue",
  413. "statsPeriod": "14d",
  414. }
  415. response = self.do_request(query, features=features)
  416. assert response.status_code == 200, response.content
  417. assert len(response.data["data"]) == 1
  418. assert response.data["data"][0]["issue"] == "unknown"
  419. @pytest.mark.skip("Cannot look up group_id of transaction events")
  420. def test_unknown_issue(self):
  421. project = self.create_project()
  422. event = self.store_event(
  423. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  424. )
  425. data = load_data("transaction", timestamp=before_now(minutes=1))
  426. self.store_event(data, project_id=project.id)
  427. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  428. query = {"field": ["project", "issue"], "query": "issue:unknown", "statsPeriod": "14d"}
  429. response = self.do_request(query, features=features)
  430. assert response.status_code == 200, response.content
  431. assert len(response.data["data"]) == 1
  432. assert response.data["data"][0]["issue"] == "unknown"
  433. query = {"field": ["project", "issue"], "query": "!issue:unknown", "statsPeriod": "14d"}
  434. response = self.do_request(query, features=features)
  435. assert response.status_code == 200, response.content
  436. assert len(response.data["data"]) == 1
  437. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  438. def test_negative_user_search(self):
  439. project = self.create_project()
  440. user_data = {"email": "foo@example.com", "id": "123", "username": "foo"}
  441. # Load an event with data that shouldn't match
  442. data = load_data("transaction", timestamp=before_now(minutes=1))
  443. data["transaction"] = "/transactions/nomatch"
  444. event_user = user_data.copy()
  445. event_user["id"] = "undefined"
  446. data["user"] = event_user
  447. self.store_event(data, project_id=project.id)
  448. # Load a matching event
  449. data = load_data("transaction", timestamp=before_now(minutes=1))
  450. data["transaction"] = "/transactions/matching"
  451. data["user"] = user_data
  452. self.store_event(data, project_id=project.id)
  453. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  454. query = {
  455. "field": ["project", "user"],
  456. "query": '!user:"id:undefined"',
  457. "statsPeriod": "14d",
  458. }
  459. response = self.do_request(query, features=features)
  460. assert response.status_code == 200, response.content
  461. assert len(response.data["data"]) == 1
  462. assert response.data["data"][0]["user"] == "id:{}".format(user_data["id"])
  463. assert "user.email" not in response.data["data"][0]
  464. assert "user.id" not in response.data["data"][0]
  465. def test_not_project_in_query(self):
  466. project1 = self.create_project()
  467. project2 = self.create_project()
  468. self.store_event(
  469. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  470. project_id=project1.id,
  471. )
  472. self.store_event(
  473. data={"event_id": "b" * 32, "environment": "staging", "timestamp": self.min_ago},
  474. project_id=project2.id,
  475. )
  476. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  477. query = {
  478. "field": ["project", "count()"],
  479. "query": '!project:"%s"' % project1.slug,
  480. "statsPeriod": "14d",
  481. }
  482. response = self.do_request(query, features=features)
  483. assert response.status_code == 200, response.content
  484. assert len(response.data["data"]) == 1
  485. assert response.data["data"][0]["project"] == project2.slug
  486. assert "project.id" not in response.data["data"][0]
  487. def test_error_handled_condition(self):
  488. self.login_as(user=self.user)
  489. project = self.create_project()
  490. prototype = load_data("android-ndk")
  491. events = (
  492. ("a" * 32, "not handled", False),
  493. ("b" * 32, "was handled", True),
  494. ("c" * 32, "undefined", None),
  495. )
  496. for event in events:
  497. prototype["event_id"] = event[0]
  498. prototype["message"] = event[1]
  499. prototype["exception"]["values"][0]["value"] = event[1]
  500. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  501. prototype["timestamp"] = self.two_min_ago
  502. self.store_event(data=prototype, project_id=project.id)
  503. with self.feature("organizations:discover-basic"):
  504. query = {
  505. "field": ["message", "error.handled"],
  506. "query": "error.handled:0",
  507. "orderby": "message",
  508. }
  509. response = self.do_request(query)
  510. assert response.status_code == 200, response.data
  511. assert 1 == len(response.data["data"])
  512. assert [0] == response.data["data"][0]["error.handled"]
  513. with self.feature("organizations:discover-basic"):
  514. query = {
  515. "field": ["message", "error.handled"],
  516. "query": "error.handled:1",
  517. "orderby": "message",
  518. }
  519. response = self.do_request(query)
  520. assert response.status_code == 200, response.data
  521. assert 2 == len(response.data["data"])
  522. assert [None] == response.data["data"][0]["error.handled"]
  523. assert [1] == response.data["data"][1]["error.handled"]
  524. def test_error_unhandled_condition(self):
  525. self.login_as(user=self.user)
  526. project = self.create_project()
  527. prototype = load_data("android-ndk")
  528. events = (
  529. ("a" * 32, "not handled", False),
  530. ("b" * 32, "was handled", True),
  531. ("c" * 32, "undefined", None),
  532. )
  533. for event in events:
  534. prototype["event_id"] = event[0]
  535. prototype["message"] = event[1]
  536. prototype["exception"]["values"][0]["value"] = event[1]
  537. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  538. prototype["timestamp"] = self.two_min_ago
  539. self.store_event(data=prototype, project_id=project.id)
  540. with self.feature("organizations:discover-basic"):
  541. query = {
  542. "field": ["message", "error.unhandled", "error.handled"],
  543. "query": "error.unhandled:true",
  544. "orderby": "message",
  545. }
  546. response = self.do_request(query)
  547. assert response.status_code == 200, response.data
  548. assert 1 == len(response.data["data"])
  549. assert [0] == response.data["data"][0]["error.handled"]
  550. assert 1 == response.data["data"][0]["error.unhandled"]
  551. with self.feature("organizations:discover-basic"):
  552. query = {
  553. "field": ["message", "error.handled", "error.unhandled"],
  554. "query": "error.unhandled:false",
  555. "orderby": "message",
  556. }
  557. response = self.do_request(query)
  558. assert response.status_code == 200, response.data
  559. assert 2 == len(response.data["data"])
  560. assert [None] == response.data["data"][0]["error.handled"]
  561. assert 0 == response.data["data"][0]["error.unhandled"]
  562. assert [1] == response.data["data"][1]["error.handled"]
  563. assert 0 == response.data["data"][1]["error.unhandled"]
  564. def test_implicit_groupby(self):
  565. project = self.create_project()
  566. self.store_event(
  567. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  568. project_id=project.id,
  569. )
  570. event1 = self.store_event(
  571. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_1"]},
  572. project_id=project.id,
  573. )
  574. event2 = self.store_event(
  575. data={"event_id": "c" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  576. project_id=project.id,
  577. )
  578. query = {"field": ["count(id)", "project.id", "issue.id"], "orderby": "issue.id"}
  579. response = self.do_request(query)
  580. assert response.status_code == 200, response.content
  581. assert len(response.data["data"]) == 2
  582. data = response.data["data"]
  583. assert data[0] == {"project.id": project.id, "issue.id": event1.group_id, "count_id": 2}
  584. assert data[1] == {"project.id": project.id, "issue.id": event2.group_id, "count_id": 1}
  585. meta = response.data["meta"]
  586. assert meta["count_id"] == "integer"
  587. def test_orderby(self):
  588. project = self.create_project()
  589. self.store_event(
  590. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  591. )
  592. self.store_event(
  593. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project.id
  594. )
  595. self.store_event(
  596. data={"event_id": "c" * 32, "timestamp": self.min_ago}, project_id=project.id
  597. )
  598. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  599. response = self.do_request(query)
  600. assert response.status_code == 200, response.content
  601. data = response.data["data"]
  602. assert data[0]["id"] == "c" * 32
  603. assert data[1]["id"] == "b" * 32
  604. assert data[2]["id"] == "a" * 32
  605. def test_sort_title(self):
  606. project = self.create_project()
  607. self.store_event(
  608. data={"event_id": "a" * 32, "message": "zlast", "timestamp": self.two_min_ago},
  609. project_id=project.id,
  610. )
  611. self.store_event(
  612. data={"event_id": "b" * 32, "message": "second", "timestamp": self.min_ago},
  613. project_id=project.id,
  614. )
  615. self.store_event(
  616. data={"event_id": "c" * 32, "message": "first", "timestamp": self.min_ago},
  617. project_id=project.id,
  618. )
  619. query = {"field": ["id", "title"], "sort": "title"}
  620. response = self.do_request(query)
  621. assert response.status_code == 200, response.content
  622. data = response.data["data"]
  623. assert data[0]["id"] == "c" * 32
  624. assert data[1]["id"] == "b" * 32
  625. assert data[2]["id"] == "a" * 32
  626. def test_sort_invalid(self):
  627. project = self.create_project()
  628. self.store_event(
  629. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  630. )
  631. query = {"field": ["id"], "sort": "garbage"}
  632. response = self.do_request(query)
  633. assert response.status_code == 400
  634. assert "order by" in response.data["detail"]
  635. def test_latest_release_alias(self):
  636. project = self.create_project()
  637. event1 = self.store_event(
  638. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "release": "0.8"},
  639. project_id=project.id,
  640. )
  641. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  642. response = self.do_request(query)
  643. assert response.status_code == 200, response.content
  644. data = response.data["data"]
  645. assert data[0]["issue.id"] == event1.group_id
  646. assert data[0]["release"] == "0.8"
  647. event2 = self.store_event(
  648. data={"event_id": "a" * 32, "timestamp": self.min_ago, "release": "0.9"},
  649. project_id=project.id,
  650. )
  651. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  652. response = self.do_request(query)
  653. assert response.status_code == 200, response.content
  654. data = response.data["data"]
  655. assert data[0]["issue.id"] == event2.group_id
  656. assert data[0]["release"] == "0.9"
  657. def test_semver(self):
  658. release_1 = self.create_release(version="test@1.2.3")
  659. release_2 = self.create_release(version="test@1.2.4")
  660. release_3 = self.create_release(version="test@1.2.5")
  661. release_1_e_1 = self.store_event(
  662. data={"release": release_1.version, "timestamp": self.min_ago},
  663. project_id=self.project.id,
  664. ).event_id
  665. release_1_e_2 = self.store_event(
  666. data={"release": release_1.version, "timestamp": self.min_ago},
  667. project_id=self.project.id,
  668. ).event_id
  669. release_2_e_1 = self.store_event(
  670. data={"release": release_2.version, "timestamp": self.min_ago},
  671. project_id=self.project.id,
  672. ).event_id
  673. release_2_e_2 = self.store_event(
  674. data={"release": release_2.version, "timestamp": self.min_ago},
  675. project_id=self.project.id,
  676. ).event_id
  677. release_3_e_1 = self.store_event(
  678. data={"release": release_3.version, "timestamp": self.min_ago},
  679. project_id=self.project.id,
  680. ).event_id
  681. release_3_e_2 = self.store_event(
  682. data={"release": release_3.version, "timestamp": self.min_ago},
  683. project_id=self.project.id,
  684. ).event_id
  685. query = {"field": ["id"], "query": f"{SEMVER_ALIAS}:>1.2.3"}
  686. response = self.do_request(query)
  687. assert response.status_code == 200, response.content
  688. assert {r["id"] for r in response.data["data"]} == {
  689. release_2_e_1,
  690. release_2_e_2,
  691. release_3_e_1,
  692. release_3_e_2,
  693. }
  694. query = {"field": ["id"], "query": f"{SEMVER_ALIAS}:>=1.2.3"}
  695. response = self.do_request(query)
  696. assert response.status_code == 200, response.content
  697. assert {r["id"] for r in response.data["data"]} == {
  698. release_1_e_1,
  699. release_1_e_2,
  700. release_2_e_1,
  701. release_2_e_2,
  702. release_3_e_1,
  703. release_3_e_2,
  704. }
  705. query = {"field": ["id"], "query": f"{SEMVER_ALIAS}:<1.2.4"}
  706. response = self.do_request(query)
  707. assert response.status_code == 200, response.content
  708. assert {r["id"] for r in response.data["data"]} == {
  709. release_1_e_1,
  710. release_1_e_2,
  711. }
  712. def test_semver_package(self):
  713. release_1 = self.create_release(version="test@1.2.3")
  714. release_2 = self.create_release(version="test2@1.2.4")
  715. release_1_e_1 = self.store_event(
  716. data={"release": release_1.version, "timestamp": self.min_ago},
  717. project_id=self.project.id,
  718. ).event_id
  719. release_1_e_2 = self.store_event(
  720. data={"release": release_1.version, "timestamp": self.min_ago},
  721. project_id=self.project.id,
  722. ).event_id
  723. release_2_e_1 = self.store_event(
  724. data={"release": release_2.version, "timestamp": self.min_ago},
  725. project_id=self.project.id,
  726. ).event_id
  727. query = {"field": ["id"], "query": f"{SEMVER_PACKAGE_ALIAS}:test"}
  728. response = self.do_request(query)
  729. assert response.status_code == 200, response.content
  730. assert {r["id"] for r in response.data["data"]} == {
  731. release_1_e_1,
  732. release_1_e_2,
  733. }
  734. query = {"field": ["id"], "query": f"{SEMVER_PACKAGE_ALIAS}:test2"}
  735. response = self.do_request(query)
  736. assert response.status_code == 200, response.content
  737. assert {r["id"] for r in response.data["data"]} == {
  738. release_2_e_1,
  739. }
  740. def test_aliased_fields(self):
  741. project = self.create_project()
  742. event1 = self.store_event(
  743. data={
  744. "event_id": "a" * 32,
  745. "timestamp": self.min_ago,
  746. "fingerprint": ["group_1"],
  747. "user": {"email": "foo@example.com"},
  748. },
  749. project_id=project.id,
  750. )
  751. event2 = self.store_event(
  752. data={
  753. "event_id": "b" * 32,
  754. "timestamp": self.min_ago,
  755. "fingerprint": ["group_2"],
  756. "user": {"email": "foo@example.com"},
  757. },
  758. project_id=project.id,
  759. )
  760. self.store_event(
  761. data={
  762. "event_id": "c" * 32,
  763. "timestamp": self.min_ago,
  764. "fingerprint": ["group_2"],
  765. "user": {"email": "bar@example.com"},
  766. },
  767. project_id=project.id,
  768. )
  769. query = {"field": ["issue.id", "count(id)", "count_unique(user)"], "orderby": "issue.id"}
  770. response = self.do_request(query)
  771. assert response.status_code == 200, response.content
  772. assert len(response.data["data"]) == 2
  773. data = response.data["data"]
  774. assert data[0]["issue.id"] == event1.group_id
  775. assert data[0]["count_id"] == 1
  776. assert data[0]["count_unique_user"] == 1
  777. assert "projectid" not in data[0]
  778. assert "project.id" not in data[0]
  779. assert data[1]["issue.id"] == event2.group_id
  780. assert data[1]["count_id"] == 2
  781. assert data[1]["count_unique_user"] == 2
  782. def test_aggregate_field_with_dotted_param(self):
  783. project = self.create_project()
  784. event1 = self.store_event(
  785. data={
  786. "event_id": "a" * 32,
  787. "timestamp": self.min_ago,
  788. "fingerprint": ["group_1"],
  789. "user": {"id": "123", "email": "foo@example.com"},
  790. },
  791. project_id=project.id,
  792. )
  793. event2 = self.store_event(
  794. data={
  795. "event_id": "b" * 32,
  796. "timestamp": self.min_ago,
  797. "fingerprint": ["group_2"],
  798. "user": {"id": "123", "email": "foo@example.com"},
  799. },
  800. project_id=project.id,
  801. )
  802. self.store_event(
  803. data={
  804. "event_id": "c" * 32,
  805. "timestamp": self.min_ago,
  806. "fingerprint": ["group_2"],
  807. "user": {"id": "456", "email": "bar@example.com"},
  808. },
  809. project_id=project.id,
  810. )
  811. query = {
  812. "field": ["issue.id", "issue_title", "count(id)", "count_unique(user.email)"],
  813. "orderby": "issue.id",
  814. }
  815. response = self.do_request(query)
  816. assert response.status_code == 200, response.content
  817. assert len(response.data["data"]) == 2
  818. data = response.data["data"]
  819. assert data[0]["issue.id"] == event1.group_id
  820. assert data[0]["count_id"] == 1
  821. assert data[0]["count_unique_user_email"] == 1
  822. assert "projectid" not in data[0]
  823. assert "project.id" not in data[0]
  824. assert data[1]["issue.id"] == event2.group_id
  825. assert data[1]["count_id"] == 2
  826. assert data[1]["count_unique_user_email"] == 2
  827. def test_failure_rate_alias_field(self):
  828. project = self.create_project()
  829. data = load_data("transaction", timestamp=before_now(minutes=1))
  830. data["transaction"] = "/failure_rate/success"
  831. self.store_event(data, project_id=project.id)
  832. data = load_data("transaction", timestamp=before_now(minutes=1))
  833. data["transaction"] = "/failure_rate/unknown"
  834. data["contexts"]["trace"]["status"] = "unknown_error"
  835. self.store_event(data, project_id=project.id)
  836. for i in range(6):
  837. data = load_data("transaction", timestamp=before_now(minutes=1))
  838. data["transaction"] = f"/failure_rate/{i}"
  839. data["contexts"]["trace"]["status"] = "unauthenticated"
  840. self.store_event(data, project_id=project.id)
  841. query = {"field": ["failure_rate()"], "query": "event.type:transaction"}
  842. response = self.do_request(query)
  843. assert response.status_code == 200, response.content
  844. assert len(response.data["data"]) == 1
  845. data = response.data["data"]
  846. assert data[0]["failure_rate"] == 0.75
  847. def test_count_miserable_alias_field(self):
  848. project = self.create_project()
  849. events = [
  850. ("one", 300),
  851. ("one", 300),
  852. ("two", 3000),
  853. ("two", 3000),
  854. ("three", 300),
  855. ("three", 3000),
  856. ]
  857. for idx, event in enumerate(events):
  858. data = load_data(
  859. "transaction",
  860. timestamp=before_now(minutes=(1 + idx)),
  861. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  862. )
  863. data["event_id"] = f"{idx}" * 32
  864. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  865. data["user"] = {"email": f"{event[0]}@example.com"}
  866. self.store_event(data, project_id=project.id)
  867. query = {"field": ["count_miserable(user, 300)"], "query": "event.type:transaction"}
  868. response = self.do_request(query)
  869. assert response.status_code == 200, response.content
  870. assert len(response.data["data"]) == 1
  871. data = response.data["data"]
  872. assert data[0]["count_miserable_user_300"] == 2
  873. @mock.patch(
  874. "sentry.search.events.fields.MAX_QUERYABLE_TRANSACTION_THRESHOLDS",
  875. MAX_QUERYABLE_TRANSACTION_THRESHOLDS,
  876. )
  877. def test_too_many_transaction_thresholds(self):
  878. project_transaction_thresholds = []
  879. project_ids = []
  880. for i in range(MAX_QUERYABLE_TRANSACTION_THRESHOLDS + 1):
  881. project = self.create_project(name=f"bulk_txn_{i}")
  882. project_ids.append(project.id)
  883. project_transaction_thresholds.append(
  884. ProjectTransactionThreshold(
  885. organization=self.organization,
  886. project=project,
  887. threshold=400,
  888. metric=TransactionMetric.LCP.value,
  889. )
  890. )
  891. ProjectTransactionThreshold.objects.bulk_create(project_transaction_thresholds)
  892. events = [
  893. ("one", 400),
  894. ("one", 400),
  895. ("two", 3000),
  896. ("two", 3000),
  897. ("three", 300),
  898. ("three", 3000),
  899. ]
  900. for idx, event in enumerate(events):
  901. data = load_data(
  902. "transaction",
  903. timestamp=before_now(minutes=(1 + idx)),
  904. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  905. )
  906. data["event_id"] = f"{idx}" * 32
  907. data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
  908. data["user"] = {"email": f"{idx}@example.com"}
  909. self.store_event(data, project_id=project_ids[0])
  910. query = {
  911. "field": [
  912. "transaction",
  913. "count_miserable(user)",
  914. ],
  915. "query": "event.type:transaction",
  916. "project": project_ids,
  917. }
  918. response = self.do_request(
  919. query,
  920. features={
  921. "organizations:discover-basic": True,
  922. "organizations:project-transaction-threshold": True,
  923. "organizations:global-views": True,
  924. },
  925. )
  926. assert response.status_code == 400
  927. assert (
  928. response.data["detail"]
  929. == "Exceeded 1 configured transaction thresholds limit, try with fewer Projects."
  930. )
  931. def test_count_miserable_new_alias_field(self):
  932. project = self.create_project()
  933. ProjectTransactionThreshold.objects.create(
  934. project=project,
  935. organization=project.organization,
  936. threshold=400,
  937. metric=TransactionMetric.DURATION.value,
  938. )
  939. events = [
  940. ("one", 400),
  941. ("one", 400),
  942. ("two", 3000),
  943. ("two", 3000),
  944. ("three", 300),
  945. ("three", 3000),
  946. ]
  947. for idx, event in enumerate(events):
  948. data = load_data(
  949. "transaction",
  950. timestamp=before_now(minutes=(1 + idx)),
  951. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  952. )
  953. data["event_id"] = f"{idx}" * 32
  954. data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
  955. data["user"] = {"email": f"{idx}@example.com"}
  956. self.store_event(data, project_id=project.id)
  957. query = {
  958. "field": [
  959. "transaction",
  960. "count_miserable(user)",
  961. ],
  962. "query": "event.type:transaction",
  963. "project": [project.id],
  964. }
  965. # Cannot access it without feature enabled
  966. response = self.do_request(query)
  967. assert response.status_code == 404
  968. response = self.do_request(
  969. query,
  970. features={
  971. "organizations:discover-basic": True,
  972. "organizations:project-transaction-threshold": True,
  973. },
  974. )
  975. assert response.status_code == 200, response.content
  976. assert len(response.data["data"]) == 3
  977. data = response.data["data"]
  978. assert data[0]["count_miserable_user"] == 0
  979. assert data[1]["count_miserable_user"] == 2
  980. assert data[2]["count_miserable_user"] == 1
  981. query["query"] = "event.type:transaction count_miserable(user):>0"
  982. response = self.do_request(
  983. query,
  984. features={
  985. "organizations:discover-basic": True,
  986. "organizations:project-transaction-threshold": True,
  987. },
  988. )
  989. assert response.status_code == 200, response.content
  990. assert len(response.data["data"]) == 2
  991. data = response.data["data"]
  992. assert abs(data[0]["count_miserable_user"]) == 2
  993. assert abs(data[1]["count_miserable_user"]) == 1
  994. def test_user_misery_alias_field(self):
  995. project = self.create_project()
  996. events = [
  997. ("one", 300),
  998. ("one", 300),
  999. ("two", 3000),
  1000. ("two", 3000),
  1001. ("three", 300),
  1002. ("three", 3000),
  1003. ]
  1004. for idx, event in enumerate(events):
  1005. data = load_data(
  1006. "transaction",
  1007. timestamp=before_now(minutes=(1 + idx)),
  1008. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1009. )
  1010. data["event_id"] = f"{idx}" * 32
  1011. data["transaction"] = f"/user_misery/{idx}"
  1012. data["user"] = {"email": f"{event[0]}@example.com"}
  1013. self.store_event(data, project_id=project.id)
  1014. query = {"field": ["user_misery(300)"], "query": "event.type:transaction"}
  1015. response = self.do_request(query)
  1016. assert response.status_code == 200, response.content
  1017. assert len(response.data["data"]) == 1
  1018. data = response.data["data"]
  1019. assert abs(data[0]["user_misery_300"] - 0.0653) < 0.0001
  1020. def test_apdex_new_alias_field(self):
  1021. project = self.create_project()
  1022. ProjectTransactionThreshold.objects.create(
  1023. project=project,
  1024. organization=project.organization,
  1025. threshold=400,
  1026. metric=TransactionMetric.DURATION.value,
  1027. )
  1028. events = [
  1029. ("one", 400),
  1030. ("one", 400),
  1031. ("two", 3000),
  1032. ("two", 3000),
  1033. ("three", 300),
  1034. ("three", 3000),
  1035. ]
  1036. for idx, event in enumerate(events):
  1037. data = load_data(
  1038. "transaction",
  1039. timestamp=before_now(minutes=(1 + idx)),
  1040. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1041. )
  1042. data["event_id"] = f"{idx}" * 32
  1043. data["transaction"] = f"/apdex/new/{event[0]}"
  1044. data["user"] = {"email": f"{idx}@example.com"}
  1045. self.store_event(data, project_id=project.id)
  1046. query = {
  1047. "field": [
  1048. "transaction",
  1049. "apdex()",
  1050. ],
  1051. "query": "event.type:transaction",
  1052. "project": [project.id],
  1053. }
  1054. # Cannot access it without feature enabled
  1055. response = self.do_request(query)
  1056. assert response.status_code == 404
  1057. response = self.do_request(
  1058. query,
  1059. features={
  1060. "organizations:discover-basic": True,
  1061. "organizations:project-transaction-threshold": True,
  1062. },
  1063. )
  1064. assert response.status_code == 200, response.content
  1065. assert len(response.data["data"]) == 3
  1066. data = response.data["data"]
  1067. assert data[0]["apdex"] == 1.0
  1068. assert data[1]["apdex"] == 0.5
  1069. assert data[2]["apdex"] == 0.0
  1070. query["query"] = "event.type:transaction apdex():>0.50"
  1071. response = self.do_request(
  1072. query,
  1073. features={
  1074. "organizations:discover-basic": True,
  1075. "organizations:project-transaction-threshold": True,
  1076. },
  1077. )
  1078. assert response.status_code == 200, response.content
  1079. assert len(response.data["data"]) == 1
  1080. data = response.data["data"]
  1081. assert data[0]["apdex"] == 1.0
  1082. def test_user_misery_alias_field_with_project_threshold(self):
  1083. project = self.create_project()
  1084. ProjectTransactionThreshold.objects.create(
  1085. project=project,
  1086. organization=project.organization,
  1087. threshold=400,
  1088. metric=TransactionMetric.DURATION.value,
  1089. )
  1090. events = [
  1091. ("one", 400),
  1092. ("one", 400),
  1093. ("two", 3000),
  1094. ("two", 3000),
  1095. ("three", 300),
  1096. ("three", 3000),
  1097. ]
  1098. for idx, event in enumerate(events):
  1099. data = load_data(
  1100. "transaction",
  1101. timestamp=before_now(minutes=(1 + idx)),
  1102. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1103. )
  1104. data["event_id"] = f"{idx}" * 32
  1105. data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
  1106. data["user"] = {"email": f"{idx}@example.com"}
  1107. self.store_event(data, project_id=project.id)
  1108. query = {
  1109. "field": [
  1110. "transaction",
  1111. "user_misery()",
  1112. ],
  1113. "query": "event.type:transaction",
  1114. "project": [project.id],
  1115. }
  1116. # Cannot access it without feature enabled
  1117. response = self.do_request(query)
  1118. assert response.status_code == 404
  1119. response = self.do_request(
  1120. query,
  1121. features={
  1122. "organizations:discover-basic": True,
  1123. "organizations:project-transaction-threshold": True,
  1124. },
  1125. )
  1126. assert response.status_code == 200, response.content
  1127. assert len(response.data["data"]) == 3
  1128. data = response.data["data"]
  1129. assert abs(data[0]["user_misery"] - 0.04916) < 0.0001
  1130. assert abs(data[1]["user_misery"] - 0.06586) < 0.0001
  1131. assert abs(data[2]["user_misery"] - 0.05751) < 0.0001
  1132. query["query"] = "event.type:transaction user_misery():>0.050"
  1133. response = self.do_request(
  1134. query,
  1135. features={
  1136. "organizations:discover-basic": True,
  1137. "organizations:project-transaction-threshold": True,
  1138. },
  1139. )
  1140. assert response.status_code == 200, response.content
  1141. assert len(response.data["data"]) == 2
  1142. data = response.data["data"]
  1143. assert abs(data[0]["user_misery"] - 0.06586) < 0.0001
  1144. assert abs(data[1]["user_misery"] - 0.05751) < 0.0001
  1145. def test_user_misery_alias_field_with_transaction_threshold(self):
  1146. project = self.create_project()
  1147. events = [
  1148. ("one", 300),
  1149. ("two", 300),
  1150. ("one", 3000),
  1151. ("two", 3000),
  1152. ("three", 400),
  1153. ("four", 4000),
  1154. ]
  1155. for idx, event in enumerate(events):
  1156. data = load_data(
  1157. "transaction",
  1158. timestamp=before_now(minutes=(1 + idx)),
  1159. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1160. )
  1161. data["event_id"] = f"{idx}" * 32
  1162. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  1163. data["user"] = {"email": f"{event[0]}@example.com"}
  1164. self.store_event(data, project_id=project.id)
  1165. if idx % 2:
  1166. ProjectTransactionThresholdOverride.objects.create(
  1167. transaction=f"/count_miserable/horribilis/{idx}",
  1168. project=project,
  1169. organization=project.organization,
  1170. threshold=100 * idx,
  1171. metric=TransactionMetric.DURATION.value,
  1172. )
  1173. query = {
  1174. "field": [
  1175. "transaction",
  1176. "user_misery()",
  1177. ],
  1178. "query": "event.type:transaction",
  1179. "orderby": "transaction",
  1180. "project": [project.id],
  1181. }
  1182. response = self.do_request(
  1183. query,
  1184. features={
  1185. "organizations:discover-basic": True,
  1186. "organizations:project-transaction-threshold": True,
  1187. },
  1188. )
  1189. assert response.status_code == 200, response.content
  1190. expected = [
  1191. ("/count_miserable/horribilis/0", ["duration", 300], 0.049578),
  1192. ("/count_miserable/horribilis/1", ["duration", 100], 0.049578),
  1193. ("/count_miserable/horribilis/2", ["duration", 300], 0.058),
  1194. ("/count_miserable/horribilis/3", ["duration", 300], 0.058),
  1195. ("/count_miserable/horribilis/4", ["duration", 300], 0.049578),
  1196. ("/count_miserable/horribilis/5", ["duration", 500], 0.058),
  1197. ]
  1198. assert len(response.data["data"]) == 6
  1199. data = response.data["data"]
  1200. for i, record in enumerate(expected):
  1201. name, threshold_config, misery = record
  1202. assert data[i]["transaction"] == name
  1203. assert data[i]["project_threshold_config"] == threshold_config
  1204. assert abs(data[i]["user_misery"] - misery) < 0.0001
  1205. query["query"] = "event.type:transaction user_misery():>0.050"
  1206. response = self.do_request(
  1207. query,
  1208. features={
  1209. "organizations:discover-basic": True,
  1210. "organizations:project-transaction-threshold": True,
  1211. },
  1212. )
  1213. assert response.status_code == 200, response.content
  1214. assert len(response.data["data"]) == 3
  1215. data = response.data["data"]
  1216. assert abs(data[0]["user_misery"] - 0.058) < 0.0001
  1217. assert abs(data[1]["user_misery"] - 0.058) < 0.0001
  1218. assert abs(data[2]["user_misery"] - 0.058) < 0.0001
  1219. def test_user_misery_alias_field_with_transaction_threshold_and_project_threshold(self):
  1220. project = self.create_project()
  1221. ProjectTransactionThreshold.objects.create(
  1222. project=project,
  1223. organization=project.organization,
  1224. threshold=100,
  1225. metric=TransactionMetric.DURATION.value,
  1226. )
  1227. events = [
  1228. ("one", 300),
  1229. ("two", 300),
  1230. ("one", 3000),
  1231. ("two", 3000),
  1232. ("three", 400),
  1233. ("four", 4000),
  1234. ]
  1235. for idx, event in enumerate(events):
  1236. data = load_data(
  1237. "transaction",
  1238. timestamp=before_now(minutes=(1 + idx)),
  1239. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  1240. )
  1241. data["event_id"] = f"{idx}" * 32
  1242. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  1243. data["user"] = {"email": f"{event[0]}@example.com"}
  1244. self.store_event(data, project_id=project.id)
  1245. if idx % 2:
  1246. ProjectTransactionThresholdOverride.objects.create(
  1247. transaction=f"/count_miserable/horribilis/{idx}",
  1248. project=project,
  1249. organization=project.organization,
  1250. threshold=100 * idx,
  1251. metric=TransactionMetric.DURATION.value,
  1252. )
  1253. project2 = self.create_project()
  1254. data = load_data("transaction", timestamp=before_now(minutes=1))
  1255. data["transaction"] = "/count_miserable/horribilis/project2"
  1256. data["user"] = {"email": "project2@example.com"}
  1257. self.store_event(data, project_id=project2.id)
  1258. query = {
  1259. "field": [
  1260. "transaction",
  1261. "user_misery()",
  1262. ],
  1263. "query": "event.type:transaction",
  1264. "orderby": "transaction",
  1265. "project": [project.id, project2.id],
  1266. }
  1267. response = self.do_request(
  1268. query,
  1269. features={
  1270. "organizations:discover-basic": True,
  1271. "organizations:global-views": True,
  1272. "organizations:project-transaction-threshold": True,
  1273. },
  1274. )
  1275. assert response.status_code == 200, response.content
  1276. expected = [
  1277. (
  1278. "/count_miserable/horribilis/0",
  1279. ["duration", 100],
  1280. 0.049578,
  1281. ), # Uses project threshold
  1282. ("/count_miserable/horribilis/1", ["duration", 100], 0.049578), # Uses txn threshold
  1283. ("/count_miserable/horribilis/2", ["duration", 100], 0.058), # Uses project threshold
  1284. ("/count_miserable/horribilis/3", ["duration", 300], 0.058), # Uses txn threshold
  1285. (
  1286. "/count_miserable/horribilis/4",
  1287. ["duration", 100],
  1288. 0.049578,
  1289. ), # Uses project threshold
  1290. ("/count_miserable/horribilis/5", ["duration", 500], 0.058), # Uses txn threshold
  1291. ("/count_miserable/horribilis/project2", ["duration", 300], 0.058), # Uses fallback
  1292. ]
  1293. assert len(response.data["data"]) == 7
  1294. data = response.data["data"]
  1295. for i, record in enumerate(expected):
  1296. name, threshold_config, misery = record
  1297. assert data[i]["transaction"] == name
  1298. assert data[i]["project_threshold_config"] == threshold_config
  1299. assert abs(data[i]["user_misery"] - misery) < 0.0001
  1300. query["query"] = "event.type:transaction user_misery():>0.050"
  1301. response = self.do_request(
  1302. query,
  1303. features={
  1304. "organizations:discover-basic": True,
  1305. "organizations:project-transaction-threshold": True,
  1306. "organizations:global-views": True,
  1307. },
  1308. )
  1309. assert response.status_code == 200, response.content
  1310. assert len(response.data["data"]) == 4
  1311. def test_aggregation(self):
  1312. project = self.create_project()
  1313. self.store_event(
  1314. data={
  1315. "event_id": "a" * 32,
  1316. "timestamp": self.min_ago,
  1317. "fingerprint": ["group_1"],
  1318. "user": {"email": "foo@example.com"},
  1319. "environment": "prod",
  1320. "tags": {"sub_customer.is-Enterprise-42": "1"},
  1321. },
  1322. project_id=project.id,
  1323. )
  1324. self.store_event(
  1325. data={
  1326. "event_id": "b" * 32,
  1327. "timestamp": self.min_ago,
  1328. "fingerprint": ["group_2"],
  1329. "user": {"email": "foo@example.com"},
  1330. "environment": "staging",
  1331. "tags": {"sub_customer.is-Enterprise-42": "1"},
  1332. },
  1333. project_id=project.id,
  1334. )
  1335. self.store_event(
  1336. data={
  1337. "event_id": "c" * 32,
  1338. "timestamp": self.min_ago,
  1339. "fingerprint": ["group_2"],
  1340. "user": {"email": "foo@example.com"},
  1341. "environment": "prod",
  1342. "tags": {"sub_customer.is-Enterprise-42": "0"},
  1343. },
  1344. project_id=project.id,
  1345. )
  1346. self.store_event(
  1347. data={
  1348. "event_id": "d" * 32,
  1349. "timestamp": self.min_ago,
  1350. "fingerprint": ["group_2"],
  1351. "user": {"email": "foo@example.com"},
  1352. "environment": "prod",
  1353. "tags": {"sub_customer.is-Enterprise-42": "1"},
  1354. },
  1355. project_id=project.id,
  1356. )
  1357. query = {
  1358. "field": ["sub_customer.is-Enterprise-42", "count(sub_customer.is-Enterprise-42)"],
  1359. "orderby": "sub_customer.is-Enterprise-42",
  1360. }
  1361. response = self.do_request(query)
  1362. assert response.status_code == 200, response.content
  1363. assert len(response.data["data"]) == 2
  1364. data = response.data["data"]
  1365. assert data[0]["count_sub_customer_is_Enterprise_42"] == 1
  1366. assert data[1]["count_sub_customer_is_Enterprise_42"] == 3
  1367. def test_aggregation_comparison(self):
  1368. project = self.create_project()
  1369. self.store_event(
  1370. data={
  1371. "event_id": "a" * 32,
  1372. "timestamp": self.min_ago,
  1373. "fingerprint": ["group_1"],
  1374. "user": {"email": "foo@example.com"},
  1375. },
  1376. project_id=project.id,
  1377. )
  1378. event = self.store_event(
  1379. data={
  1380. "event_id": "b" * 32,
  1381. "timestamp": self.min_ago,
  1382. "fingerprint": ["group_2"],
  1383. "user": {"email": "foo@example.com"},
  1384. },
  1385. project_id=project.id,
  1386. )
  1387. self.store_event(
  1388. data={
  1389. "event_id": "c" * 32,
  1390. "timestamp": self.min_ago,
  1391. "fingerprint": ["group_2"],
  1392. "user": {"email": "bar@example.com"},
  1393. },
  1394. project_id=project.id,
  1395. )
  1396. self.store_event(
  1397. data={
  1398. "event_id": "d" * 32,
  1399. "timestamp": self.min_ago,
  1400. "fingerprint": ["group_3"],
  1401. "user": {"email": "bar@example.com"},
  1402. },
  1403. project_id=project.id,
  1404. )
  1405. self.store_event(
  1406. data={
  1407. "event_id": "e" * 32,
  1408. "timestamp": self.min_ago,
  1409. "fingerprint": ["group_3"],
  1410. "user": {"email": "bar@example.com"},
  1411. },
  1412. project_id=project.id,
  1413. )
  1414. query = {
  1415. "field": ["issue.id", "count(id)", "count_unique(user)"],
  1416. "query": "count(id):>1 count_unique(user):>1",
  1417. "orderby": "issue.id",
  1418. }
  1419. response = self.do_request(query)
  1420. assert response.status_code == 200, response.content
  1421. assert len(response.data["data"]) == 1
  1422. data = response.data["data"]
  1423. assert data[0]["issue.id"] == event.group_id
  1424. assert data[0]["count_id"] == 2
  1425. assert data[0]["count_unique_user"] == 2
  1426. def test_aggregation_alias_comparison(self):
  1427. project = self.create_project()
  1428. data = load_data(
  1429. "transaction",
  1430. timestamp=before_now(minutes=1),
  1431. start_timestamp=before_now(minutes=1, seconds=5),
  1432. )
  1433. data["transaction"] = "/aggregates/1"
  1434. self.store_event(data, project_id=project.id)
  1435. data = load_data(
  1436. "transaction",
  1437. timestamp=before_now(minutes=1),
  1438. start_timestamp=before_now(minutes=1, seconds=3),
  1439. )
  1440. data["transaction"] = "/aggregates/2"
  1441. event = self.store_event(data, project_id=project.id)
  1442. query = {
  1443. "field": ["transaction", "p95()"],
  1444. "query": "event.type:transaction p95():<4000",
  1445. "orderby": ["transaction"],
  1446. }
  1447. response = self.do_request(query)
  1448. assert response.status_code == 200, response.content
  1449. assert len(response.data["data"]) == 1
  1450. data = response.data["data"]
  1451. assert data[0]["transaction"] == event.transaction
  1452. assert data[0]["p95"] == 3000
  1453. def test_aggregation_comparison_with_conditions(self):
  1454. project = self.create_project()
  1455. self.store_event(
  1456. data={
  1457. "event_id": "a" * 32,
  1458. "timestamp": self.min_ago,
  1459. "fingerprint": ["group_1"],
  1460. "user": {"email": "foo@example.com"},
  1461. "environment": "prod",
  1462. },
  1463. project_id=project.id,
  1464. )
  1465. self.store_event(
  1466. data={
  1467. "event_id": "b" * 32,
  1468. "timestamp": self.min_ago,
  1469. "fingerprint": ["group_2"],
  1470. "user": {"email": "foo@example.com"},
  1471. "environment": "staging",
  1472. },
  1473. project_id=project.id,
  1474. )
  1475. event = self.store_event(
  1476. data={
  1477. "event_id": "c" * 32,
  1478. "timestamp": self.min_ago,
  1479. "fingerprint": ["group_2"],
  1480. "user": {"email": "foo@example.com"},
  1481. "environment": "prod",
  1482. },
  1483. project_id=project.id,
  1484. )
  1485. self.store_event(
  1486. data={
  1487. "event_id": "d" * 32,
  1488. "timestamp": self.min_ago,
  1489. "fingerprint": ["group_2"],
  1490. "user": {"email": "foo@example.com"},
  1491. "environment": "prod",
  1492. },
  1493. project_id=project.id,
  1494. )
  1495. query = {
  1496. "field": ["issue.id", "count(id)"],
  1497. "query": "count(id):>1 user.email:foo@example.com environment:prod",
  1498. "orderby": "issue.id",
  1499. }
  1500. response = self.do_request(query)
  1501. assert response.status_code == 200, response.content
  1502. assert len(response.data["data"]) == 1
  1503. data = response.data["data"]
  1504. assert data[0]["issue.id"] == event.group_id
  1505. assert data[0]["count_id"] == 2
  1506. def test_aggregation_date_comparison_with_conditions(self):
  1507. project = self.create_project()
  1508. event = self.store_event(
  1509. data={
  1510. "event_id": "a" * 32,
  1511. "timestamp": self.min_ago,
  1512. "fingerprint": ["group_1"],
  1513. "user": {"email": "foo@example.com"},
  1514. "environment": "prod",
  1515. },
  1516. project_id=project.id,
  1517. )
  1518. self.store_event(
  1519. data={
  1520. "event_id": "b" * 32,
  1521. "timestamp": self.min_ago,
  1522. "fingerprint": ["group_2"],
  1523. "user": {"email": "foo@example.com"},
  1524. "environment": "staging",
  1525. },
  1526. project_id=project.id,
  1527. )
  1528. self.store_event(
  1529. data={
  1530. "event_id": "c" * 32,
  1531. "timestamp": self.min_ago,
  1532. "fingerprint": ["group_2"],
  1533. "user": {"email": "foo@example.com"},
  1534. "environment": "prod",
  1535. },
  1536. project_id=project.id,
  1537. )
  1538. self.store_event(
  1539. data={
  1540. "event_id": "d" * 32,
  1541. "timestamp": self.min_ago,
  1542. "fingerprint": ["group_2"],
  1543. "user": {"email": "foo@example.com"},
  1544. "environment": "prod",
  1545. },
  1546. project_id=project.id,
  1547. )
  1548. query = {
  1549. "field": ["issue.id", "max(timestamp)"],
  1550. "query": "max(timestamp):>1 user.email:foo@example.com environment:prod",
  1551. "orderby": "issue.id",
  1552. }
  1553. response = self.do_request(query)
  1554. assert response.status_code == 200, response.content
  1555. assert len(response.data["data"]) == 2
  1556. response.data["meta"]["max_timestamp"] == "date"
  1557. data = response.data["data"]
  1558. assert data[0]["issue.id"] == event.group_id
  1559. def test_percentile_function(self):
  1560. project = self.create_project()
  1561. data = load_data(
  1562. "transaction",
  1563. timestamp=before_now(minutes=1),
  1564. start_timestamp=before_now(minutes=1, seconds=5),
  1565. )
  1566. data["transaction"] = "/aggregates/1"
  1567. event1 = self.store_event(data, project_id=project.id)
  1568. data = load_data(
  1569. "transaction",
  1570. timestamp=before_now(minutes=1),
  1571. start_timestamp=before_now(minutes=1, seconds=3),
  1572. )
  1573. data["transaction"] = "/aggregates/2"
  1574. event2 = self.store_event(data, project_id=project.id)
  1575. query = {
  1576. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  1577. "query": "event.type:transaction",
  1578. "orderby": ["transaction"],
  1579. }
  1580. response = self.do_request(query)
  1581. assert response.status_code == 200, response.content
  1582. assert len(response.data["data"]) == 2
  1583. data = response.data["data"]
  1584. assert data[0]["transaction"] == event1.transaction
  1585. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  1586. assert data[1]["transaction"] == event2.transaction
  1587. assert data[1]["percentile_transaction_duration_0_95"] == 3000
  1588. def test_percentile_function_as_condition(self):
  1589. project = self.create_project()
  1590. data = load_data(
  1591. "transaction",
  1592. timestamp=before_now(minutes=1),
  1593. start_timestamp=before_now(minutes=1, seconds=5),
  1594. )
  1595. data["transaction"] = "/aggregates/1"
  1596. event1 = self.store_event(data, project_id=project.id)
  1597. data = load_data(
  1598. "transaction",
  1599. timestamp=before_now(minutes=1),
  1600. start_timestamp=before_now(minutes=1, seconds=3),
  1601. )
  1602. data["transaction"] = "/aggregates/2"
  1603. self.store_event(data, project_id=project.id)
  1604. query = {
  1605. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  1606. "query": "event.type:transaction percentile(transaction.duration, 0.95):>4000",
  1607. "orderby": ["transaction"],
  1608. }
  1609. response = self.do_request(query)
  1610. assert response.status_code == 200, response.content
  1611. assert len(response.data["data"]) == 1
  1612. data = response.data["data"]
  1613. assert data[0]["transaction"] == event1.transaction
  1614. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  1615. def test_epm_function(self):
  1616. project = self.create_project()
  1617. data = load_data(
  1618. "transaction",
  1619. timestamp=before_now(minutes=1),
  1620. start_timestamp=before_now(minutes=1, seconds=5),
  1621. )
  1622. data["transaction"] = "/aggregates/1"
  1623. event1 = self.store_event(data, project_id=project.id)
  1624. data = load_data(
  1625. "transaction",
  1626. timestamp=before_now(minutes=1),
  1627. start_timestamp=before_now(minutes=1, seconds=3),
  1628. )
  1629. data["transaction"] = "/aggregates/2"
  1630. event2 = self.store_event(data, project_id=project.id)
  1631. query = {
  1632. "field": ["transaction", "epm()"],
  1633. "query": "event.type:transaction",
  1634. "orderby": ["transaction"],
  1635. "statsPeriod": "2m",
  1636. }
  1637. response = self.do_request(query)
  1638. assert response.status_code == 200, response.content
  1639. assert len(response.data["data"]) == 2
  1640. data = response.data["data"]
  1641. assert data[0]["transaction"] == event1.transaction
  1642. assert data[0]["epm"] == 0.5
  1643. assert data[1]["transaction"] == event2.transaction
  1644. assert data[1]["epm"] == 0.5
  1645. def test_nonexistent_fields(self):
  1646. project = self.create_project()
  1647. self.store_event(
  1648. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1649. project_id=project.id,
  1650. )
  1651. query = {"field": ["issue_world.id"]}
  1652. response = self.do_request(query)
  1653. assert response.status_code == 200, response.content
  1654. assert response.data["data"][0]["issue_world.id"] == ""
  1655. def test_no_requested_fields_or_grouping(self):
  1656. project = self.create_project()
  1657. self.store_event(
  1658. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1659. project_id=project.id,
  1660. )
  1661. query = {"query": "test"}
  1662. response = self.do_request(query)
  1663. assert response.status_code == 400, response.content
  1664. assert response.data["detail"] == "No columns selected"
  1665. def test_condition_on_aggregate_misses(self):
  1666. project = self.create_project()
  1667. self.store_event(
  1668. data={
  1669. "event_id": "c" * 32,
  1670. "timestamp": self.min_ago,
  1671. "fingerprint": ["group_2"],
  1672. "user": {"email": "bar@example.com"},
  1673. },
  1674. project_id=project.id,
  1675. )
  1676. query = {"field": ["issue.id"], "query": "event_count:>0", "orderby": "issue.id"}
  1677. response = self.do_request(query)
  1678. assert response.status_code == 200, response.content
  1679. assert len(response.data["data"]) == 0
  1680. def test_next_prev_link_headers(self):
  1681. project = self.create_project()
  1682. events = [("a", "group_1"), ("b", "group_2"), ("c", "group_2"), ("d", "group_2")]
  1683. for e in events:
  1684. self.store_event(
  1685. data={
  1686. "event_id": e[0] * 32,
  1687. "timestamp": self.min_ago,
  1688. "fingerprint": [e[1]],
  1689. "user": {"email": "foo@example.com"},
  1690. "tags": {"language": "C++"},
  1691. },
  1692. project_id=project.id,
  1693. )
  1694. query = {
  1695. "field": ["count(id)", "issue.id", "context.key"],
  1696. "sort": "-count_id",
  1697. "query": "language:C++",
  1698. }
  1699. response = self.do_request(query)
  1700. assert response.status_code == 200, response.content
  1701. links = parse_link_header(response["Link"])
  1702. for link in links:
  1703. assert "field=issue.id" in link
  1704. assert "field=count%28id%29" in link
  1705. assert "field=context.key" in link
  1706. assert "sort=-count_id" in link
  1707. assert "query=language%3AC%2B%2B" in link
  1708. assert len(response.data["data"]) == 2
  1709. data = response.data["data"]
  1710. assert data[0]["count_id"] == 3
  1711. assert data[1]["count_id"] == 1
  1712. def test_empty_count_query(self):
  1713. project = self.create_project()
  1714. event = self.store_event(
  1715. data={
  1716. "event_id": "a" * 32,
  1717. "timestamp": iso_format(before_now(minutes=5)),
  1718. "fingerprint": ["1123581321"],
  1719. "user": {"email": "foo@example.com"},
  1720. "tags": {"language": "C++"},
  1721. },
  1722. project_id=project.id,
  1723. )
  1724. query = {
  1725. "field": ["count()"],
  1726. "query": "issue.id:%d timestamp:>%s" % (event.group_id, self.min_ago),
  1727. "statsPeriod": "14d",
  1728. }
  1729. response = self.do_request(query)
  1730. assert response.status_code == 200, response.content
  1731. data = response.data["data"]
  1732. assert len(data) == 1
  1733. assert data[0]["count"] == 0
  1734. def test_stack_wildcard_condition(self):
  1735. project = self.create_project()
  1736. data = load_data("javascript")
  1737. data["timestamp"] = self.min_ago
  1738. self.store_event(data=data, project_id=project.id)
  1739. query = {"field": ["stack.filename", "message"], "query": "stack.filename:*.js"}
  1740. response = self.do_request(query)
  1741. assert response.status_code == 200, response.content
  1742. assert len(response.data["data"]) == 1
  1743. assert response.data["meta"]["message"] == "string"
  1744. def test_email_wildcard_condition(self):
  1745. project = self.create_project()
  1746. data = load_data("javascript")
  1747. data["timestamp"] = self.min_ago
  1748. self.store_event(data=data, project_id=project.id)
  1749. query = {"field": ["stack.filename", "message"], "query": "user.email:*@example.org"}
  1750. response = self.do_request(query)
  1751. assert response.status_code == 200, response.content
  1752. assert len(response.data["data"]) == 1
  1753. assert response.data["meta"]["message"] == "string"
  1754. def test_transaction_event_type(self):
  1755. project = self.create_project()
  1756. data = load_data(
  1757. "transaction",
  1758. timestamp=before_now(minutes=1),
  1759. start_timestamp=before_now(minutes=1, seconds=5),
  1760. )
  1761. self.store_event(data=data, project_id=project.id)
  1762. query = {
  1763. "field": ["transaction", "transaction.duration", "transaction.status"],
  1764. "query": "event.type:transaction",
  1765. }
  1766. response = self.do_request(query)
  1767. assert response.status_code == 200, response.content
  1768. assert len(response.data["data"]) == 1
  1769. assert response.data["meta"]["transaction.duration"] == "duration"
  1770. assert response.data["meta"]["transaction.status"] == "string"
  1771. assert response.data["data"][0]["transaction.status"] == "ok"
  1772. def test_trace_columns(self):
  1773. project = self.create_project()
  1774. data = load_data(
  1775. "transaction",
  1776. timestamp=before_now(minutes=1),
  1777. start_timestamp=before_now(minutes=1, seconds=5),
  1778. )
  1779. self.store_event(data=data, project_id=project.id)
  1780. query = {"field": ["trace"], "query": "event.type:transaction"}
  1781. response = self.do_request(query)
  1782. assert response.status_code == 200, response.content
  1783. assert len(response.data["data"]) == 1
  1784. assert response.data["meta"]["trace"] == "string"
  1785. assert response.data["data"][0]["trace"] == data["contexts"]["trace"]["trace_id"]
  1786. def test_issue_in_columns(self):
  1787. project1 = self.create_project()
  1788. project2 = self.create_project()
  1789. event1 = self.store_event(
  1790. data={
  1791. "event_id": "a" * 32,
  1792. "transaction": "/example",
  1793. "message": "how to make fast",
  1794. "timestamp": self.two_min_ago,
  1795. "fingerprint": ["group_1"],
  1796. },
  1797. project_id=project1.id,
  1798. )
  1799. event2 = self.store_event(
  1800. data={
  1801. "event_id": "b" * 32,
  1802. "transaction": "/example",
  1803. "message": "how to make fast",
  1804. "timestamp": self.two_min_ago,
  1805. "fingerprint": ["group_1"],
  1806. },
  1807. project_id=project2.id,
  1808. )
  1809. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1810. query = {"field": ["id", "issue"], "orderby": ["id"]}
  1811. response = self.do_request(query, features=features)
  1812. assert response.status_code == 200, response.content
  1813. data = response.data["data"]
  1814. assert len(data) == 2
  1815. assert data[0]["id"] == event1.event_id
  1816. assert data[0]["issue.id"] == event1.group_id
  1817. assert data[0]["issue"] == event1.group.qualified_short_id
  1818. assert data[1]["id"] == event2.event_id
  1819. assert data[1]["issue.id"] == event2.group_id
  1820. assert data[1]["issue"] == event2.group.qualified_short_id
  1821. def test_issue_in_search_and_columns(self):
  1822. project1 = self.create_project()
  1823. project2 = self.create_project()
  1824. event1 = self.store_event(
  1825. data={
  1826. "event_id": "a" * 32,
  1827. "transaction": "/example",
  1828. "message": "how to make fast",
  1829. "timestamp": self.two_min_ago,
  1830. "fingerprint": ["group_1"],
  1831. },
  1832. project_id=project1.id,
  1833. )
  1834. self.store_event(
  1835. data={
  1836. "event_id": "b" * 32,
  1837. "transaction": "/example",
  1838. "message": "how to make fast",
  1839. "timestamp": self.two_min_ago,
  1840. "fingerprint": ["group_1"],
  1841. },
  1842. project_id=project2.id,
  1843. )
  1844. tests = [
  1845. ("issue", "issue:%s" % event1.group.qualified_short_id),
  1846. ("issue.id", "issue:%s" % event1.group.qualified_short_id),
  1847. ("issue", "issue.id:%s" % event1.group_id),
  1848. ("issue.id", "issue.id:%s" % event1.group_id),
  1849. ]
  1850. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1851. for testdata in tests:
  1852. query = {"field": [testdata[0]], "query": testdata[1]}
  1853. response = self.do_request(query, features=features)
  1854. assert response.status_code == 200, response.content
  1855. data = response.data["data"]
  1856. assert len(data) == 1
  1857. assert data[0]["id"] == event1.event_id
  1858. assert data[0]["issue.id"] == event1.group_id
  1859. if testdata[0] == "issue":
  1860. assert data[0]["issue"] == event1.group.qualified_short_id
  1861. else:
  1862. assert data[0].get("issue", None) is None
  1863. def test_issue_negation(self):
  1864. project1 = self.create_project()
  1865. project2 = self.create_project()
  1866. event1 = self.store_event(
  1867. data={
  1868. "event_id": "a" * 32,
  1869. "transaction": "/example",
  1870. "message": "how to make fast",
  1871. "timestamp": self.two_min_ago,
  1872. "fingerprint": ["group_1"],
  1873. },
  1874. project_id=project1.id,
  1875. )
  1876. event2 = self.store_event(
  1877. data={
  1878. "event_id": "b" * 32,
  1879. "transaction": "/example",
  1880. "message": "go really fast plz",
  1881. "timestamp": self.two_min_ago,
  1882. "fingerprint": ["group_2"],
  1883. },
  1884. project_id=project2.id,
  1885. )
  1886. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1887. query = {
  1888. "field": ["title", "issue.id"],
  1889. "query": f"!issue:{event1.group.qualified_short_id}",
  1890. }
  1891. response = self.do_request(query, features=features)
  1892. assert response.status_code == 200, response.content
  1893. data = response.data["data"]
  1894. assert len(data) == 1
  1895. assert data[0]["title"] == event2.title
  1896. assert data[0]["issue.id"] == event2.group_id
  1897. def test_search_for_nonexistent_issue(self):
  1898. project1 = self.create_project()
  1899. self.store_event(
  1900. data={
  1901. "event_id": "a" * 32,
  1902. "transaction": "/example",
  1903. "message": "how to make fast",
  1904. "timestamp": self.two_min_ago,
  1905. "fingerprint": ["group_1"],
  1906. },
  1907. project_id=project1.id,
  1908. )
  1909. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1910. query = {"field": ["count()"], "query": "issue.id:112358"}
  1911. response = self.do_request(query, features=features)
  1912. assert response.status_code == 200, response.content
  1913. data = response.data["data"]
  1914. assert len(data) == 1
  1915. assert data[0]["count"] == 0
  1916. def test_issue_alias_inside_aggregate(self):
  1917. project1 = self.create_project()
  1918. self.store_event(
  1919. data={
  1920. "event_id": "a" * 32,
  1921. "transaction": "/example",
  1922. "message": "how to make fast",
  1923. "timestamp": self.two_min_ago,
  1924. "fingerprint": ["group_1"],
  1925. },
  1926. project_id=project1.id,
  1927. )
  1928. self.store_event(
  1929. data={
  1930. "event_id": "b" * 32,
  1931. "transaction": "/example",
  1932. "message": "how to make fast",
  1933. "timestamp": self.two_min_ago,
  1934. "fingerprint": ["group_2"],
  1935. },
  1936. project_id=project1.id,
  1937. )
  1938. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1939. query = {
  1940. "field": ["project", "count(id)", "count_unique(issue.id)", "count_unique(issue)"],
  1941. "sort": "-count(id)",
  1942. "statsPeriod": "24h",
  1943. }
  1944. response = self.do_request(query, features=features)
  1945. assert response.status_code == 200, response.content
  1946. data = response.data["data"]
  1947. assert len(data) == 1
  1948. assert data[0]["count_id"] == 2
  1949. assert data[0]["count_unique_issue_id"] == 2
  1950. assert data[0]["count_unique_issue"] == 2
  1951. def test_project_alias_inside_aggregate(self):
  1952. project1 = self.create_project()
  1953. project2 = self.create_project()
  1954. self.store_event(
  1955. data={
  1956. "event_id": "a" * 32,
  1957. "transaction": "/example",
  1958. "message": "how to make fast",
  1959. "timestamp": self.two_min_ago,
  1960. "fingerprint": ["group_1"],
  1961. },
  1962. project_id=project1.id,
  1963. )
  1964. self.store_event(
  1965. data={
  1966. "event_id": "b" * 32,
  1967. "transaction": "/example",
  1968. "message": "how to make fast",
  1969. "timestamp": self.two_min_ago,
  1970. "fingerprint": ["group_2"],
  1971. },
  1972. project_id=project2.id,
  1973. )
  1974. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1975. query = {
  1976. "field": [
  1977. "event.type",
  1978. "count(id)",
  1979. "count_unique(project.id)",
  1980. "count_unique(project)",
  1981. ],
  1982. "sort": "-count(id)",
  1983. "statsPeriod": "24h",
  1984. }
  1985. response = self.do_request(query, features=features)
  1986. assert response.status_code == 200, response.content
  1987. data = response.data["data"]
  1988. assert len(data) == 1
  1989. assert data[0]["count_id"] == 2
  1990. assert data[0]["count_unique_project_id"] == 2
  1991. assert data[0]["count_unique_project"] == 2
  1992. def test_user_display(self):
  1993. project1 = self.create_project()
  1994. project2 = self.create_project()
  1995. self.store_event(
  1996. data={
  1997. "event_id": "a" * 32,
  1998. "transaction": "/example",
  1999. "message": "how to make fast",
  2000. "timestamp": self.two_min_ago,
  2001. "user": {"email": "cathy@example.com"},
  2002. },
  2003. project_id=project1.id,
  2004. )
  2005. self.store_event(
  2006. data={
  2007. "event_id": "b" * 32,
  2008. "transaction": "/example",
  2009. "message": "how to make fast",
  2010. "timestamp": self.two_min_ago,
  2011. "user": {"username": "catherine"},
  2012. },
  2013. project_id=project2.id,
  2014. )
  2015. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2016. query = {
  2017. "field": ["event.type", "user.display"],
  2018. "query": "user.display:cath*",
  2019. "statsPeriod": "24h",
  2020. }
  2021. response = self.do_request(query, features=features)
  2022. assert response.status_code == 200, response.content
  2023. data = response.data["data"]
  2024. assert len(data) == 2
  2025. result = {r["user.display"] for r in data}
  2026. assert result == {"catherine", "cathy@example.com"}
  2027. def test_user_display_with_aggregates(self):
  2028. self.login_as(user=self.user)
  2029. project1 = self.create_project()
  2030. self.store_event(
  2031. data={
  2032. "event_id": "a" * 32,
  2033. "transaction": "/example",
  2034. "message": "how to make fast",
  2035. "timestamp": self.two_min_ago,
  2036. "user": {"email": "cathy@example.com"},
  2037. },
  2038. project_id=project1.id,
  2039. )
  2040. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2041. query = {
  2042. "field": ["event.type", "user.display", "count_unique(title)"],
  2043. "statsPeriod": "24h",
  2044. }
  2045. response = self.do_request(query, features=features)
  2046. assert response.status_code == 200, response.content
  2047. data = response.data["data"]
  2048. assert len(data) == 1
  2049. result = {r["user.display"] for r in data}
  2050. assert result == {"cathy@example.com"}
  2051. query = {"field": ["event.type", "count_unique(user.display)"], "statsPeriod": "24h"}
  2052. response = self.do_request(query, features=features)
  2053. assert response.status_code == 200, response.content
  2054. data = response.data["data"]
  2055. assert len(data) == 1
  2056. assert data[0]["count_unique_user_display"] == 1
  2057. def test_orderby_user_display(self):
  2058. project1 = self.create_project()
  2059. project2 = self.create_project()
  2060. self.store_event(
  2061. data={
  2062. "event_id": "a" * 32,
  2063. "transaction": "/example",
  2064. "message": "how to make fast",
  2065. "timestamp": self.two_min_ago,
  2066. "user": {"email": "cathy@example.com"},
  2067. },
  2068. project_id=project1.id,
  2069. )
  2070. self.store_event(
  2071. data={
  2072. "event_id": "b" * 32,
  2073. "transaction": "/example",
  2074. "message": "how to make fast",
  2075. "timestamp": self.two_min_ago,
  2076. "user": {"username": "catherine"},
  2077. },
  2078. project_id=project2.id,
  2079. )
  2080. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2081. query = {
  2082. "field": ["event.type", "user.display"],
  2083. "query": "user.display:cath*",
  2084. "statsPeriod": "24h",
  2085. "orderby": "-user.display",
  2086. }
  2087. response = self.do_request(query, features=features)
  2088. assert response.status_code == 200, response.content
  2089. data = response.data["data"]
  2090. assert len(data) == 2
  2091. result = [r["user.display"] for r in data]
  2092. # because we're ordering by `-user.display`, we expect the results in reverse sorted order
  2093. assert result == ["cathy@example.com", "catherine"]
  2094. def test_orderby_user_display_with_aggregates(self):
  2095. project1 = self.create_project()
  2096. project2 = self.create_project()
  2097. self.store_event(
  2098. data={
  2099. "event_id": "a" * 32,
  2100. "transaction": "/example",
  2101. "message": "how to make fast",
  2102. "timestamp": self.two_min_ago,
  2103. "user": {"email": "cathy@example.com"},
  2104. },
  2105. project_id=project1.id,
  2106. )
  2107. self.store_event(
  2108. data={
  2109. "event_id": "b" * 32,
  2110. "transaction": "/example",
  2111. "message": "how to make fast",
  2112. "timestamp": self.two_min_ago,
  2113. "user": {"username": "catherine"},
  2114. },
  2115. project_id=project2.id,
  2116. )
  2117. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2118. query = {
  2119. "field": ["event.type", "user.display", "count_unique(title)"],
  2120. "query": "user.display:cath*",
  2121. "statsPeriod": "24h",
  2122. "orderby": "user.display",
  2123. }
  2124. response = self.do_request(query, features=features)
  2125. assert response.status_code == 200, response.content
  2126. data = response.data["data"]
  2127. assert len(data) == 2
  2128. result = [r["user.display"] for r in data]
  2129. # because we're ordering by `user.display`, we expect the results in sorted order
  2130. assert result == ["catherine", "cathy@example.com"]
  2131. def test_any_field_alias(self):
  2132. day_ago = before_now(days=1).replace(hour=10, minute=11, second=12, microsecond=13)
  2133. project1 = self.create_project()
  2134. self.store_event(
  2135. data={
  2136. "event_id": "a" * 32,
  2137. "transaction": "/example",
  2138. "message": "how to make fast",
  2139. "timestamp": iso_format(day_ago),
  2140. "user": {"email": "cathy@example.com"},
  2141. },
  2142. project_id=project1.id,
  2143. )
  2144. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2145. query = {
  2146. "field": [
  2147. "event.type",
  2148. "any(user.display)",
  2149. "any(timestamp.to_day)",
  2150. "any(timestamp.to_hour)",
  2151. ],
  2152. "statsPeriod": "7d",
  2153. }
  2154. response = self.do_request(query, features=features)
  2155. assert response.status_code == 200, response.content
  2156. data = response.data["data"]
  2157. assert len(data) == 1
  2158. result = {r["any_user_display"] for r in data}
  2159. assert result == {"cathy@example.com"}
  2160. result = {r["any_timestamp_to_day"][:19] for r in data}
  2161. assert result == {iso_format(day_ago.replace(hour=0, minute=0, second=0, microsecond=0))}
  2162. result = {r["any_timestamp_to_hour"][:19] for r in data}
  2163. assert result == {iso_format(day_ago.replace(minute=0, second=0, microsecond=0))}
  2164. def test_field_aliases_in_conflicting_functions(self):
  2165. day_ago = before_now(days=1).replace(hour=10, minute=11, second=12, microsecond=13)
  2166. project1 = self.create_project()
  2167. self.store_event(
  2168. data={
  2169. "event_id": "a" * 32,
  2170. "transaction": "/example",
  2171. "message": "how to make fast",
  2172. "timestamp": iso_format(day_ago),
  2173. "user": {"email": "cathy@example.com"},
  2174. },
  2175. project_id=project1.id,
  2176. )
  2177. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2178. field_aliases = ["user.display", "timestamp.to_day", "timestamp.to_hour"]
  2179. for alias in field_aliases:
  2180. query = {
  2181. "field": [alias, f"any({alias})"],
  2182. "statsPeriod": "7d",
  2183. }
  2184. response = self.do_request(query, features=features)
  2185. assert response.status_code == 400, response.content
  2186. assert (
  2187. response.data["detail"]
  2188. == 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})"
  2189. )
  2190. @pytest.mark.skip(
  2191. """
  2192. For some reason ClickHouse errors when there are two of the same string literals
  2193. (in this case the empty string "") in a query and one is in the prewhere clause.
  2194. Does not affect production or ClickHouse versions > 20.4.
  2195. """
  2196. )
  2197. def test_has_message(self):
  2198. project = self.create_project()
  2199. event = self.store_event(
  2200. {"timestamp": iso_format(before_now(minutes=1)), "message": "a"}, project_id=project.id
  2201. )
  2202. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2203. query = {"field": ["project", "message"], "query": "has:message", "statsPeriod": "14d"}
  2204. response = self.do_request(query, features=features)
  2205. assert response.status_code == 200, response.content
  2206. assert len(response.data["data"]) == 1
  2207. assert response.data["data"][0]["message"] == event.message
  2208. query = {"field": ["project", "message"], "query": "!has:message", "statsPeriod": "14d"}
  2209. response = self.do_request(query, features=features)
  2210. assert response.status_code == 200, response.content
  2211. assert len(response.data["data"]) == 0
  2212. def test_has_transaction_status(self):
  2213. project = self.create_project()
  2214. data = load_data("transaction", timestamp=before_now(minutes=1))
  2215. data["transaction"] = "/transactionstatus/1"
  2216. self.store_event(data, project_id=project.id)
  2217. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2218. query = {
  2219. "field": ["event.type", "count(id)"],
  2220. "query": "event.type:transaction has:transaction.status",
  2221. "sort": "-count(id)",
  2222. "statsPeriod": "24h",
  2223. }
  2224. response = self.do_request(query, features=features)
  2225. assert response.status_code == 200, response.content
  2226. data = response.data["data"]
  2227. assert len(data) == 1
  2228. assert data[0]["count_id"] == 1
  2229. def test_not_has_transaction_status(self):
  2230. project = self.create_project()
  2231. data = load_data("transaction", timestamp=before_now(minutes=1))
  2232. data["transaction"] = "/transactionstatus/1"
  2233. self.store_event(data, project_id=project.id)
  2234. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2235. query = {
  2236. "field": ["event.type", "count(id)"],
  2237. "query": "event.type:transaction !has:transaction.status",
  2238. "sort": "-count(id)",
  2239. "statsPeriod": "24h",
  2240. }
  2241. response = self.do_request(query, features=features)
  2242. assert response.status_code == 200, response.content
  2243. data = response.data["data"]
  2244. assert len(data) == 1
  2245. assert data[0]["count_id"] == 0
  2246. def test_tag_that_looks_like_aggregation(self):
  2247. project = self.create_project()
  2248. data = {
  2249. "message": "Failure state",
  2250. "timestamp": self.two_min_ago,
  2251. "tags": {"count_diff": 99},
  2252. }
  2253. self.store_event(data, project_id=project.id)
  2254. query = {
  2255. "field": ["message", "count_diff", "count()"],
  2256. "query": "",
  2257. "project": [project.id],
  2258. "statsPeriod": "24h",
  2259. }
  2260. response = self.do_request(query)
  2261. assert response.status_code == 200, response.content
  2262. meta = response.data["meta"]
  2263. assert "string" == meta["count_diff"], "tags should not be counted as integers"
  2264. assert "string" == meta["message"]
  2265. assert "integer" == meta["count"]
  2266. assert 1 == len(response.data["data"])
  2267. data = response.data["data"][0]
  2268. assert "99" == data["count_diff"]
  2269. assert "Failure state" == data["message"]
  2270. assert 1 == data["count"]
  2271. def test_aggregate_negation(self):
  2272. project = self.create_project()
  2273. data = load_data(
  2274. "transaction",
  2275. timestamp=before_now(minutes=1),
  2276. start_timestamp=before_now(minutes=1, seconds=5),
  2277. )
  2278. self.store_event(data, project_id=project.id)
  2279. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2280. query = {
  2281. "field": ["event.type", "p99()"],
  2282. "query": "event.type:transaction p99():5s",
  2283. "statsPeriod": "24h",
  2284. }
  2285. response = self.do_request(query, features=features)
  2286. assert response.status_code == 200, response.content
  2287. data = response.data["data"]
  2288. assert len(data) == 1
  2289. query = {
  2290. "field": ["event.type", "p99()"],
  2291. "query": "event.type:transaction !p99():5s",
  2292. "statsPeriod": "24h",
  2293. }
  2294. response = self.do_request(query, features=features)
  2295. assert response.status_code == 200, response.content
  2296. data = response.data["data"]
  2297. assert len(data) == 0
  2298. def test_all_aggregates_in_columns(self):
  2299. project = self.create_project()
  2300. data = load_data(
  2301. "transaction",
  2302. timestamp=before_now(minutes=2),
  2303. start_timestamp=before_now(minutes=2, seconds=5),
  2304. )
  2305. data["transaction"] = "/failure_rate/1"
  2306. self.store_event(data, project_id=project.id)
  2307. data = load_data(
  2308. "transaction",
  2309. timestamp=before_now(minutes=1),
  2310. start_timestamp=before_now(minutes=1, seconds=5),
  2311. )
  2312. data["transaction"] = "/failure_rate/1"
  2313. data["contexts"]["trace"]["status"] = "unauthenticated"
  2314. event = self.store_event(data, project_id=project.id)
  2315. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2316. query = {
  2317. "field": [
  2318. "event.type",
  2319. "p50()",
  2320. "p75()",
  2321. "p95()",
  2322. "p99()",
  2323. "p100()",
  2324. "percentile(transaction.duration, 0.99)",
  2325. "apdex(300)",
  2326. "count_miserable(user, 300)",
  2327. "user_misery(300)",
  2328. "failure_rate()",
  2329. ],
  2330. "query": "event.type:transaction",
  2331. }
  2332. response = self.do_request(query, features=features)
  2333. assert response.status_code == 200, response.content
  2334. meta = response.data["meta"]
  2335. assert meta["p50"] == "duration"
  2336. assert meta["p75"] == "duration"
  2337. assert meta["p95"] == "duration"
  2338. assert meta["p99"] == "duration"
  2339. assert meta["p100"] == "duration"
  2340. assert meta["percentile_transaction_duration_0_99"] == "duration"
  2341. assert meta["apdex_300"] == "number"
  2342. assert meta["failure_rate"] == "percentage"
  2343. assert meta["user_misery_300"] == "number"
  2344. assert meta["count_miserable_user_300"] == "number"
  2345. data = response.data["data"]
  2346. assert len(data) == 1
  2347. assert data[0]["p50"] == 5000
  2348. assert data[0]["p75"] == 5000
  2349. assert data[0]["p95"] == 5000
  2350. assert data[0]["p99"] == 5000
  2351. assert data[0]["p100"] == 5000
  2352. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2353. assert data[0]["apdex_300"] == 0.0
  2354. assert data[0]["count_miserable_user_300"] == 1
  2355. assert data[0]["user_misery_300"] == 0.058
  2356. assert data[0]["failure_rate"] == 0.5
  2357. features = {
  2358. "organizations:discover-basic": True,
  2359. "organizations:global-views": True,
  2360. "organizations:project-transaction-threshold": True,
  2361. }
  2362. query = {
  2363. "field": [
  2364. "event.type",
  2365. "p50()",
  2366. "p75()",
  2367. "p95()",
  2368. "p99()",
  2369. "p100()",
  2370. "percentile(transaction.duration, 0.99)",
  2371. "apdex(300)",
  2372. "apdex()",
  2373. "count_miserable(user, 300)",
  2374. "user_misery(300)",
  2375. "failure_rate()",
  2376. "count_miserable(user)",
  2377. "user_misery()",
  2378. ],
  2379. "query": "event.type:transaction",
  2380. "project": [project.id],
  2381. }
  2382. response = self.do_request(query, features=features)
  2383. assert response.status_code == 200, response.content
  2384. meta = response.data["meta"]
  2385. assert meta["p50"] == "duration"
  2386. assert meta["p75"] == "duration"
  2387. assert meta["p95"] == "duration"
  2388. assert meta["p99"] == "duration"
  2389. assert meta["p100"] == "duration"
  2390. assert meta["percentile_transaction_duration_0_99"] == "duration"
  2391. assert meta["apdex_300"] == "number"
  2392. assert meta["apdex"] == "number"
  2393. assert meta["failure_rate"] == "percentage"
  2394. assert meta["user_misery_300"] == "number"
  2395. assert meta["count_miserable_user_300"] == "number"
  2396. assert meta["project_threshold_config"] == "string"
  2397. assert meta["user_misery"] == "number"
  2398. assert meta["count_miserable_user"] == "number"
  2399. data = response.data["data"]
  2400. assert len(data) == 1
  2401. assert data[0]["p50"] == 5000
  2402. assert data[0]["p75"] == 5000
  2403. assert data[0]["p95"] == 5000
  2404. assert data[0]["p99"] == 5000
  2405. assert data[0]["p100"] == 5000
  2406. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2407. assert data[0]["apdex_300"] == 0.0
  2408. assert data[0]["apdex"] == 0.0
  2409. assert data[0]["count_miserable_user_300"] == 1
  2410. assert data[0]["user_misery_300"] == 0.058
  2411. assert data[0]["failure_rate"] == 0.5
  2412. assert data[0]["project_threshold_config"] == ["duration", 300]
  2413. assert data[0]["user_misery"] == 0.058
  2414. assert data[0]["count_miserable_user"] == 1
  2415. query = {
  2416. "field": ["event.type", "last_seen()", "latest_event()"],
  2417. "query": "event.type:transaction",
  2418. }
  2419. response = self.do_request(query, features=features)
  2420. assert response.status_code == 200, response.content
  2421. data = response.data["data"]
  2422. assert len(data) == 1
  2423. assert iso_format(before_now(minutes=1))[:-5] in data[0]["last_seen"]
  2424. assert data[0]["latest_event"] == event.event_id
  2425. query = {
  2426. "field": [
  2427. "event.type",
  2428. "count()",
  2429. "count(id)",
  2430. "count_unique(project)",
  2431. "min(transaction.duration)",
  2432. "max(transaction.duration)",
  2433. "avg(transaction.duration)",
  2434. "stddev(transaction.duration)",
  2435. "var(transaction.duration)",
  2436. "cov(transaction.duration, transaction.duration)",
  2437. "corr(transaction.duration, transaction.duration)",
  2438. "sum(transaction.duration)",
  2439. ],
  2440. "query": "event.type:transaction",
  2441. }
  2442. response = self.do_request(query, features=features)
  2443. assert response.status_code == 200, response.content
  2444. data = response.data["data"]
  2445. assert len(data) == 1
  2446. assert data[0]["count"] == 2
  2447. assert data[0]["count_id"] == 2
  2448. assert data[0]["count_unique_project"] == 1
  2449. assert data[0]["min_transaction_duration"] == 5000
  2450. assert data[0]["max_transaction_duration"] == 5000
  2451. assert data[0]["avg_transaction_duration"] == 5000
  2452. assert data[0]["stddev_transaction_duration"] == 0.0
  2453. assert data[0]["var_transaction_duration"] == 0.0
  2454. assert data[0]["cov_transaction_duration_transaction_duration"] == 0.0
  2455. assert data[0]["corr_transaction_duration_transaction_duration"] == 0.0
  2456. assert data[0]["sum_transaction_duration"] == 10000
  2457. def test_null_user_misery_returns_zero(self):
  2458. project = self.create_project()
  2459. data = load_data(
  2460. "transaction",
  2461. timestamp=before_now(minutes=2),
  2462. start_timestamp=before_now(minutes=2, seconds=5),
  2463. )
  2464. data["user"] = None
  2465. data["transaction"] = "/no_users/1"
  2466. self.store_event(data, project_id=project.id)
  2467. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2468. query = {
  2469. "field": ["user_misery(300)"],
  2470. "query": "event.type:transaction",
  2471. }
  2472. response = self.do_request(query, features=features)
  2473. assert response.status_code == 200, response.content
  2474. meta = response.data["meta"]
  2475. assert meta["user_misery_300"] == "number"
  2476. data = response.data["data"]
  2477. assert data[0]["user_misery_300"] == 0
  2478. def test_null_user_misery_new_returns_zero(self):
  2479. project = self.create_project()
  2480. data = load_data(
  2481. "transaction",
  2482. timestamp=before_now(minutes=2),
  2483. start_timestamp=before_now(minutes=2, seconds=5),
  2484. )
  2485. data["user"] = None
  2486. data["transaction"] = "/no_users/1"
  2487. self.store_event(data, project_id=project.id)
  2488. features = {
  2489. "organizations:discover-basic": True,
  2490. "organizations:project-transaction-threshold": True,
  2491. }
  2492. query = {
  2493. "field": ["user_misery()"],
  2494. "query": "event.type:transaction",
  2495. }
  2496. response = self.do_request(query, features=features)
  2497. assert response.status_code == 200, response.content
  2498. meta = response.data["meta"]
  2499. assert meta["user_misery"] == "number"
  2500. data = response.data["data"]
  2501. assert data[0]["user_misery"] == 0
  2502. def test_all_aggregates_in_query(self):
  2503. project = self.create_project()
  2504. data = load_data(
  2505. "transaction",
  2506. timestamp=before_now(minutes=2),
  2507. start_timestamp=before_now(minutes=2, seconds=5),
  2508. )
  2509. data["transaction"] = "/failure_rate/1"
  2510. self.store_event(data, project_id=project.id)
  2511. data = load_data(
  2512. "transaction",
  2513. timestamp=before_now(minutes=1),
  2514. start_timestamp=before_now(minutes=1, seconds=5),
  2515. )
  2516. data["transaction"] = "/failure_rate/2"
  2517. data["contexts"]["trace"]["status"] = "unauthenticated"
  2518. self.store_event(data, project_id=project.id)
  2519. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2520. query = {
  2521. "field": [
  2522. "event.type",
  2523. "p50()",
  2524. "p75()",
  2525. "p95()",
  2526. "percentile(transaction.duration, 0.99)",
  2527. "p100()",
  2528. ],
  2529. "query": "event.type:transaction p50():>100 p75():>1000 p95():>1000 p100():>1000 percentile(transaction.duration, 0.99):>1000",
  2530. }
  2531. response = self.do_request(query, features=features)
  2532. assert response.status_code == 200, response.content
  2533. data = response.data["data"]
  2534. assert len(data) == 1
  2535. assert data[0]["p50"] == 5000
  2536. assert data[0]["p75"] == 5000
  2537. assert data[0]["p95"] == 5000
  2538. assert data[0]["p100"] == 5000
  2539. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2540. query = {
  2541. "field": [
  2542. "event.type",
  2543. "apdex(300)",
  2544. "count_miserable(user, 300)",
  2545. "user_misery(300)",
  2546. "failure_rate()",
  2547. ],
  2548. "query": "event.type:transaction apdex(300):>-1.0 failure_rate():>0.25",
  2549. }
  2550. response = self.do_request(query, features=features)
  2551. assert response.status_code == 200, response.content
  2552. data = response.data["data"]
  2553. assert len(data) == 1
  2554. assert data[0]["apdex_300"] == 0.0
  2555. assert data[0]["count_miserable_user_300"] == 1
  2556. assert data[0]["user_misery_300"] == 0.058
  2557. assert data[0]["failure_rate"] == 0.5
  2558. query = {
  2559. "field": ["event.type", "last_seen()", "latest_event()"],
  2560. "query": "event.type:transaction last_seen():>1990-12-01T00:00:00",
  2561. }
  2562. response = self.do_request(query, features=features)
  2563. assert response.status_code == 200, response.content
  2564. data = response.data["data"]
  2565. assert len(data) == 1
  2566. query = {
  2567. "field": ["event.type", "count()", "count(id)", "count_unique(transaction)"],
  2568. "query": "event.type:transaction count():>1 count(id):>1 count_unique(transaction):>1",
  2569. }
  2570. response = self.do_request(query, features=features)
  2571. assert response.status_code == 200, response.content
  2572. data = response.data["data"]
  2573. assert len(data) == 1
  2574. assert data[0]["count"] == 2
  2575. assert data[0]["count_id"] == 2
  2576. assert data[0]["count_unique_transaction"] == 2
  2577. query = {
  2578. "field": [
  2579. "event.type",
  2580. "min(transaction.duration)",
  2581. "max(transaction.duration)",
  2582. "avg(transaction.duration)",
  2583. "sum(transaction.duration)",
  2584. "stddev(transaction.duration)",
  2585. "var(transaction.duration)",
  2586. "cov(transaction.duration, transaction.duration)",
  2587. "corr(transaction.duration, transaction.duration)",
  2588. ],
  2589. "query": " ".join(
  2590. [
  2591. "event.type:transaction",
  2592. "min(transaction.duration):>1000",
  2593. "max(transaction.duration):>1000",
  2594. "avg(transaction.duration):>1000",
  2595. "sum(transaction.duration):>1000",
  2596. "stddev(transaction.duration):>=0.0",
  2597. "var(transaction.duration):>=0.0",
  2598. "cov(transaction.duration, transaction.duration):>=0.0",
  2599. # correlation is nan because variance is 0
  2600. # "corr(transaction.duration, transaction.duration):>=0.0",
  2601. ]
  2602. ),
  2603. }
  2604. response = self.do_request(query, features=features)
  2605. assert response.status_code == 200, response.content
  2606. data = response.data["data"]
  2607. assert len(data) == 1
  2608. assert data[0]["min_transaction_duration"] == 5000
  2609. assert data[0]["max_transaction_duration"] == 5000
  2610. assert data[0]["avg_transaction_duration"] == 5000
  2611. assert data[0]["sum_transaction_duration"] == 10000
  2612. assert data[0]["stddev_transaction_duration"] == 0.0
  2613. assert data[0]["var_transaction_duration"] == 0.0
  2614. assert data[0]["cov_transaction_duration_transaction_duration"] == 0.0
  2615. assert data[0]["corr_transaction_duration_transaction_duration"] == 0.0
  2616. query = {
  2617. "field": ["event.type", "apdex(400)"],
  2618. "query": "event.type:transaction apdex(400):0",
  2619. }
  2620. response = self.do_request(query, features=features)
  2621. assert response.status_code == 200, response.content
  2622. data = response.data["data"]
  2623. assert len(data) == 1
  2624. assert data[0]["apdex_400"] == 0
  2625. def test_functions_in_orderby(self):
  2626. project = self.create_project()
  2627. data = load_data(
  2628. "transaction",
  2629. timestamp=before_now(minutes=2),
  2630. start_timestamp=before_now(minutes=2, seconds=5),
  2631. )
  2632. data["transaction"] = "/failure_rate/1"
  2633. self.store_event(data, project_id=project.id)
  2634. data = load_data(
  2635. "transaction",
  2636. timestamp=before_now(minutes=1),
  2637. start_timestamp=before_now(minutes=1, seconds=5),
  2638. )
  2639. data["transaction"] = "/failure_rate/2"
  2640. data["contexts"]["trace"]["status"] = "unauthenticated"
  2641. event = self.store_event(data, project_id=project.id)
  2642. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2643. query = {
  2644. "field": ["event.type", "p75()"],
  2645. "sort": "-p75",
  2646. "query": "event.type:transaction",
  2647. }
  2648. response = self.do_request(query, features=features)
  2649. assert response.status_code == 200, response.content
  2650. data = response.data["data"]
  2651. assert len(data) == 1
  2652. assert data[0]["p75"] == 5000
  2653. query = {
  2654. "field": ["event.type", "percentile(transaction.duration, 0.99)"],
  2655. "sort": "-percentile_transaction_duration_0_99",
  2656. "query": "event.type:transaction",
  2657. }
  2658. response = self.do_request(query, features=features)
  2659. assert response.status_code == 200, response.content
  2660. data = response.data["data"]
  2661. assert len(data) == 1
  2662. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2663. query = {
  2664. "field": ["event.type", "apdex(300)"],
  2665. "sort": "-apdex(300)",
  2666. "query": "event.type:transaction",
  2667. }
  2668. response = self.do_request(query, features=features)
  2669. assert response.status_code == 200, response.content
  2670. data = response.data["data"]
  2671. assert len(data) == 1
  2672. assert data[0]["apdex_300"] == 0.0
  2673. query = {
  2674. "field": ["event.type", "latest_event()"],
  2675. "query": "event.type:transaction",
  2676. "sort": "latest_event",
  2677. }
  2678. response = self.do_request(query, features=features)
  2679. assert response.status_code == 200, response.content
  2680. data = response.data["data"]
  2681. assert len(data) == 1
  2682. assert data[0]["latest_event"] == event.event_id
  2683. query = {
  2684. "field": ["event.type", "count_unique(transaction)"],
  2685. "query": "event.type:transaction",
  2686. "sort": "-count_unique_transaction",
  2687. }
  2688. response = self.do_request(query, features=features)
  2689. assert response.status_code == 200, response.content
  2690. data = response.data["data"]
  2691. assert len(data) == 1
  2692. assert data[0]["count_unique_transaction"] == 2
  2693. query = {
  2694. "field": ["event.type", "min(transaction.duration)"],
  2695. "query": "event.type:transaction",
  2696. "sort": "-min_transaction_duration",
  2697. }
  2698. response = self.do_request(query, features=features)
  2699. assert response.status_code == 200, response.content
  2700. data = response.data["data"]
  2701. assert len(data) == 1
  2702. assert data[0]["min_transaction_duration"] == 5000
  2703. def test_issue_alias_in_aggregate(self):
  2704. project = self.create_project()
  2705. self.store_event(
  2706. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2707. project_id=project.id,
  2708. )
  2709. self.store_event(
  2710. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  2711. project_id=project.id,
  2712. )
  2713. query = {"field": ["event.type", "count_unique(issue)"], "query": "count_unique(issue):>1"}
  2714. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2715. response = self.do_request(query, features=features)
  2716. assert response.status_code == 200, response.content
  2717. data = response.data["data"]
  2718. assert len(data) == 1
  2719. assert data[0]["count_unique_issue"] == 2
  2720. def test_deleted_issue_in_results(self):
  2721. project = self.create_project()
  2722. event1 = self.store_event(
  2723. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2724. project_id=project.id,
  2725. )
  2726. event2 = self.store_event(
  2727. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  2728. project_id=project.id,
  2729. )
  2730. event2.group.delete()
  2731. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2732. query = {"field": ["issue", "count()"], "sort": "issue"}
  2733. response = self.do_request(query, features=features)
  2734. assert response.status_code == 200, response.content
  2735. data = response.data["data"]
  2736. assert len(data) == 2
  2737. assert data[0]["issue"] == event1.group.qualified_short_id
  2738. assert data[1]["issue"] == "unknown"
  2739. def test_last_seen_negative_duration(self):
  2740. project = self.create_project()
  2741. self.store_event(
  2742. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2743. project_id=project.id,
  2744. )
  2745. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2746. query = {"field": ["id", "last_seen()"], "query": "last_seen():-30d"}
  2747. response = self.do_request(query, features=features)
  2748. assert response.status_code == 200, response.content
  2749. data = response.data["data"]
  2750. assert len(data) == 1
  2751. assert data[0]["id"] == "f" * 32
  2752. def test_last_seen_aggregate_condition(self):
  2753. project = self.create_project()
  2754. self.store_event(
  2755. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2756. project_id=project.id,
  2757. )
  2758. query = {
  2759. "field": ["id", "last_seen()"],
  2760. "query": f"last_seen():>{iso_format(before_now(days=30))}",
  2761. }
  2762. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2763. response = self.do_request(query, features=features)
  2764. assert response.status_code == 200, response.content
  2765. data = response.data["data"]
  2766. assert len(data) == 1
  2767. assert data[0]["id"] == "f" * 32
  2768. def test_conditional_filter(self):
  2769. project = self.create_project()
  2770. for v in ["a", "b"]:
  2771. self.store_event(
  2772. data={
  2773. "event_id": v * 32,
  2774. "timestamp": self.two_min_ago,
  2775. "fingerprint": ["group_1"],
  2776. },
  2777. project_id=project.id,
  2778. )
  2779. query = {
  2780. "field": ["id"],
  2781. "query": "id:{} OR id:{}".format("a" * 32, "b" * 32),
  2782. "orderby": "id",
  2783. }
  2784. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2785. response = self.do_request(query, features=features)
  2786. assert response.status_code == 200, response.content
  2787. data = response.data["data"]
  2788. assert len(data) == 2
  2789. assert data[0]["id"] == "a" * 32
  2790. assert data[1]["id"] == "b" * 32
  2791. def test_aggregation_comparison_with_conditional_filter(self):
  2792. project = self.create_project()
  2793. self.store_event(
  2794. data={
  2795. "event_id": "a" * 32,
  2796. "timestamp": self.min_ago,
  2797. "fingerprint": ["group_1"],
  2798. "user": {"email": "foo@example.com"},
  2799. "environment": "prod",
  2800. },
  2801. project_id=project.id,
  2802. )
  2803. self.store_event(
  2804. data={
  2805. "event_id": "b" * 32,
  2806. "timestamp": self.min_ago,
  2807. "fingerprint": ["group_2"],
  2808. "user": {"email": "foo@example.com"},
  2809. "environment": "staging",
  2810. },
  2811. project_id=project.id,
  2812. )
  2813. event = self.store_event(
  2814. data={
  2815. "event_id": "c" * 32,
  2816. "timestamp": self.min_ago,
  2817. "fingerprint": ["group_2"],
  2818. "user": {"email": "foo@example.com"},
  2819. "environment": "prod",
  2820. },
  2821. project_id=project.id,
  2822. )
  2823. self.store_event(
  2824. data={
  2825. "event_id": "d" * 32,
  2826. "timestamp": self.min_ago,
  2827. "fingerprint": ["group_2"],
  2828. "user": {"email": "foo@example.com"},
  2829. "environment": "canary",
  2830. },
  2831. project_id=project.id,
  2832. )
  2833. query = {
  2834. "field": ["issue.id", "count(id)"],
  2835. "query": "count(id):>1 user.email:foo@example.com AND (environment:prod OR environment:staging)",
  2836. "orderby": "issue.id",
  2837. }
  2838. response = self.do_request(query)
  2839. assert response.status_code == 200, response.content
  2840. assert len(response.data["data"]) == 1
  2841. data = response.data["data"]
  2842. assert data[0]["issue.id"] == event.group_id
  2843. assert data[0]["count_id"] == 2
  2844. def run_test_in_query(self, query, expected_events, expected_negative_events=None):
  2845. params = {
  2846. "field": ["id"],
  2847. "query": query,
  2848. "orderby": "id",
  2849. }
  2850. response = self.do_request(
  2851. params, {"organizations:discover-basic": True, "organizations:global-views": True}
  2852. )
  2853. assert response.status_code == 200, response.content
  2854. assert [row["id"] for row in response.data["data"]] == [e.event_id for e in expected_events]
  2855. if expected_negative_events is not None:
  2856. params["query"] = f"!{query}"
  2857. response = self.do_request(
  2858. params,
  2859. {"organizations:discover-basic": True, "organizations:global-views": True},
  2860. )
  2861. assert response.status_code == 200, response.content
  2862. assert [row["id"] for row in response.data["data"]] == [
  2863. e.event_id for e in expected_negative_events
  2864. ]
  2865. def test_in_query_events(self):
  2866. project_1 = self.create_project()
  2867. event_1 = self.store_event(
  2868. data={
  2869. "event_id": "a" * 32,
  2870. "timestamp": self.min_ago,
  2871. "fingerprint": ["group_1"],
  2872. "message": "group1",
  2873. "user": {"email": "hello@example.com"},
  2874. "environment": "prod",
  2875. "tags": {"random": "123"},
  2876. "release": "1.0",
  2877. },
  2878. project_id=project_1.id,
  2879. )
  2880. project_2 = self.create_project()
  2881. event_2 = self.store_event(
  2882. data={
  2883. "event_id": "b" * 32,
  2884. "timestamp": self.min_ago,
  2885. "fingerprint": ["group_2"],
  2886. "message": "group2",
  2887. "user": {"email": "bar@example.com"},
  2888. "environment": "staging",
  2889. "tags": {"random": "456"},
  2890. "stacktrace": {"frames": [{"filename": "src/app/group2.py"}]},
  2891. "release": "1.2",
  2892. },
  2893. project_id=project_2.id,
  2894. )
  2895. project_3 = self.create_project()
  2896. event_3 = self.store_event(
  2897. data={
  2898. "event_id": "c" * 32,
  2899. "timestamp": self.min_ago,
  2900. "fingerprint": ["group_3"],
  2901. "message": "group3",
  2902. "user": {"email": "foo@example.com"},
  2903. "environment": "canary",
  2904. "tags": {"random": "789"},
  2905. },
  2906. project_id=project_3.id,
  2907. )
  2908. self.run_test_in_query("environment:[prod, staging]", [event_1, event_2], [event_3])
  2909. self.run_test_in_query("environment:[staging]", [event_2], [event_1, event_3])
  2910. self.run_test_in_query(
  2911. "user.email:[foo@example.com, hello@example.com]", [event_1, event_3], [event_2]
  2912. )
  2913. self.run_test_in_query("user.email:[foo@example.com]", [event_3], [event_1, event_2])
  2914. self.run_test_in_query(
  2915. "user.display:[foo@example.com, hello@example.com]", [event_1, event_3], [event_2]
  2916. )
  2917. self.run_test_in_query("message:[group2, group1]", [event_1, event_2], [event_3])
  2918. self.run_test_in_query(
  2919. f"issue.id:[{event_1.group_id},{event_2.group_id}]", [event_1, event_2]
  2920. )
  2921. self.run_test_in_query(
  2922. f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}]",
  2923. [event_1, event_2],
  2924. )
  2925. self.run_test_in_query(
  2926. f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}, unknown]",
  2927. [event_1, event_2],
  2928. )
  2929. self.run_test_in_query(f"project_id:[{project_3.id},{project_2.id}]", [event_2, event_3])
  2930. self.run_test_in_query(
  2931. f"project.name:[{project_3.slug},{project_2.slug}]", [event_2, event_3]
  2932. )
  2933. self.run_test_in_query("random:[789,456]", [event_2, event_3], [event_1])
  2934. self.run_test_in_query("tags[random]:[789,456]", [event_2, event_3], [event_1])
  2935. self.run_test_in_query("release:[1.0,1.2]", [event_1, event_2], [event_3])
  2936. def test_in_query_events_stack(self):
  2937. project_1 = self.create_project()
  2938. test_js = self.store_event(
  2939. load_data(
  2940. "javascript",
  2941. timestamp=before_now(minutes=1),
  2942. start_timestamp=before_now(minutes=1, seconds=5),
  2943. ),
  2944. project_id=project_1.id,
  2945. )
  2946. test_java = self.store_event(
  2947. load_data(
  2948. "java",
  2949. timestamp=before_now(minutes=1),
  2950. start_timestamp=before_now(minutes=1, seconds=5),
  2951. ),
  2952. project_id=project_1.id,
  2953. )
  2954. self.run_test_in_query(
  2955. "stack.filename:[../../sentry/scripts/views.js]", [test_js], [test_java]
  2956. )
  2957. def test_in_query_transactions(self):
  2958. project = self.create_project()
  2959. data = load_data(
  2960. "transaction",
  2961. timestamp=before_now(minutes=1),
  2962. start_timestamp=before_now(minutes=1, seconds=5),
  2963. )
  2964. data["event_id"] = "a" * 32
  2965. data["contexts"]["trace"]["status"] = "ok"
  2966. transaction_1 = self.store_event(data, project_id=project.id)
  2967. data = load_data(
  2968. "transaction",
  2969. timestamp=before_now(minutes=1),
  2970. start_timestamp=before_now(minutes=1, seconds=5),
  2971. )
  2972. data["event_id"] = "b" * 32
  2973. data["contexts"]["trace"]["status"] = "aborted"
  2974. transaction_2 = self.store_event(data, project_id=project.id)
  2975. data = load_data(
  2976. "transaction",
  2977. timestamp=before_now(minutes=1),
  2978. start_timestamp=before_now(minutes=1, seconds=5),
  2979. )
  2980. data["event_id"] = "c" * 32
  2981. data["contexts"]["trace"]["status"] = "already_exists"
  2982. transaction_3 = self.store_event(data, project_id=project.id)
  2983. self.run_test_in_query(
  2984. "transaction.status:[aborted, already_exists]",
  2985. [transaction_2, transaction_3],
  2986. [transaction_1],
  2987. )
  2988. def test_messed_up_function_values(self):
  2989. # TODO (evanh): It would be nice if this surfaced an error to the user.
  2990. # The problem: The && causes the parser to treat that term not as a bad
  2991. # function call but a valid raw search with parens in it. It's not trivial
  2992. # to change the parser to recognize "bad function values" and surface them.
  2993. project = self.create_project()
  2994. for v in ["a", "b"]:
  2995. self.store_event(
  2996. data={
  2997. "event_id": v * 32,
  2998. "timestamp": self.two_min_ago,
  2999. "fingerprint": ["group_1"],
  3000. },
  3001. project_id=project.id,
  3002. )
  3003. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  3004. query = {
  3005. "field": [
  3006. "transaction",
  3007. "project",
  3008. "epm()",
  3009. "p50()",
  3010. "p95()",
  3011. "failure_rate()",
  3012. "apdex(300)",
  3013. "count_unique(user)",
  3014. "user_misery(300)",
  3015. "count_miserable(user, 300)",
  3016. ],
  3017. "query": "failure_rate():>0.003&& users:>10 event.type:transaction",
  3018. "sort": "-failure_rate",
  3019. "statsPeriod": "24h",
  3020. }
  3021. response = self.do_request(query, features=features)
  3022. assert response.status_code == 200, response.content
  3023. data = response.data["data"]
  3024. assert len(data) == 0
  3025. def test_context_fields_between_datasets(self):
  3026. project = self.create_project()
  3027. event_data = load_data("android")
  3028. transaction_data = load_data("transaction")
  3029. event_data["spans"] = transaction_data["spans"]
  3030. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  3031. event_data["type"] = "transaction"
  3032. event_data["transaction"] = "/failure_rate/1"
  3033. event_data["timestamp"] = iso_format(before_now(minutes=1))
  3034. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  3035. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  3036. self.store_event(event_data, project_id=project.id)
  3037. event_data["type"] = "error"
  3038. self.store_event(event_data, project_id=project.id)
  3039. fields = [
  3040. "os.build",
  3041. "os.kernel_version",
  3042. "device.arch",
  3043. # TODO: battery level is not consistent across both datasets
  3044. # "device.battery_level",
  3045. "device.brand",
  3046. "device.charging",
  3047. "device.locale",
  3048. "device.model_id",
  3049. "device.name",
  3050. "device.online",
  3051. "device.orientation",
  3052. "device.simulator",
  3053. "device.uuid",
  3054. ]
  3055. data = [
  3056. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  3057. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  3058. ]
  3059. for datum in data:
  3060. response = self.do_request(datum)
  3061. assert response.status_code == 200, response.content
  3062. assert len(response.data["data"]) == 1, datum
  3063. results = response.data["data"]
  3064. assert results[0]["count"] == 1, datum
  3065. for field in fields:
  3066. key, value = field.split(".", 1)
  3067. expected = str(event_data["contexts"][key][value])
  3068. assert results[0][field] == expected, field + str(datum)
  3069. def test_http_fields_between_datasets(self):
  3070. project = self.create_project()
  3071. event_data = load_data("android")
  3072. transaction_data = load_data("transaction")
  3073. event_data["spans"] = transaction_data["spans"]
  3074. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  3075. event_data["type"] = "transaction"
  3076. event_data["transaction"] = "/failure_rate/1"
  3077. event_data["timestamp"] = iso_format(before_now(minutes=1))
  3078. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  3079. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  3080. event_data["request"] = transaction_data["request"]
  3081. self.store_event(event_data, project_id=project.id)
  3082. event_data["type"] = "error"
  3083. self.store_event(event_data, project_id=project.id)
  3084. fields = ["http.method", "http.referer", "http.url"]
  3085. expected = ["GET", "fixtures.transaction", "http://countries:8010/country_by_code/"]
  3086. data = [
  3087. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  3088. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  3089. ]
  3090. for datum in data:
  3091. response = self.do_request(datum)
  3092. assert response.status_code == 200, response.content
  3093. assert len(response.data["data"]) == 1, datum
  3094. results = response.data["data"]
  3095. assert results[0]["count"] == 1, datum
  3096. for (field, exp) in zip(fields, expected):
  3097. assert results[0][field] == exp, field + str(datum)
  3098. def test_failure_count_alias_field(self):
  3099. project = self.create_project()
  3100. data = load_data("transaction", timestamp=before_now(minutes=1))
  3101. data["transaction"] = "/failure_count/success"
  3102. self.store_event(data, project_id=project.id)
  3103. data = load_data("transaction", timestamp=before_now(minutes=1))
  3104. data["transaction"] = "/failure_count/unknown"
  3105. data["contexts"]["trace"]["status"] = "unknown_error"
  3106. self.store_event(data, project_id=project.id)
  3107. for i in range(6):
  3108. data = load_data("transaction", timestamp=before_now(minutes=1))
  3109. data["transaction"] = f"/failure_count/{i}"
  3110. data["contexts"]["trace"]["status"] = "unauthenticated"
  3111. self.store_event(data, project_id=project.id)
  3112. query = {"field": ["count()", "failure_count()"], "query": "event.type:transaction"}
  3113. response = self.do_request(query)
  3114. assert response.status_code == 200, response.content
  3115. assert len(response.data["data"]) == 1
  3116. data = response.data["data"]
  3117. assert data[0]["count"] == 8
  3118. assert data[0]["failure_count"] == 6
  3119. @mock.patch("sentry.utils.snuba.quantize_time")
  3120. def test_quantize_dates(self, mock_quantize):
  3121. self.create_project()
  3122. mock_quantize.return_value = before_now(days=1).replace(tzinfo=utc)
  3123. # Don't quantize short time periods
  3124. query = {"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]}
  3125. self.do_request(query)
  3126. # Don't quantize absolute date periods
  3127. self.do_request(query)
  3128. query = {
  3129. "start": iso_format(before_now(days=20)),
  3130. "end": iso_format(before_now(days=15)),
  3131. "query": "",
  3132. "field": ["id", "timestamp"],
  3133. }
  3134. self.do_request(query)
  3135. assert len(mock_quantize.mock_calls) == 0
  3136. # Quantize long date periods
  3137. query = {"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""}
  3138. self.do_request(query)
  3139. assert len(mock_quantize.mock_calls) == 2
  3140. @mock.patch("sentry.snuba.discover.query")
  3141. def test_valid_referrer(self, mock):
  3142. mock.return_value = {}
  3143. project = self.create_project()
  3144. data = load_data("transaction", timestamp=before_now(hours=1))
  3145. self.store_event(data=data, project_id=project.id)
  3146. query = {
  3147. "field": ["user"],
  3148. "referrer": "api.performance.transaction-summary",
  3149. }
  3150. self.do_request(query)
  3151. _, kwargs = mock.call_args
  3152. self.assertEqual(kwargs["referrer"], "api.performance.transaction-summary")
  3153. @mock.patch("sentry.snuba.discover.query")
  3154. def test_invalid_referrer(self, mock):
  3155. mock.return_value = {}
  3156. project = self.create_project()
  3157. data = load_data("transaction", timestamp=before_now(hours=1))
  3158. self.store_event(data=data, project_id=project.id)
  3159. query = {
  3160. "field": ["user"],
  3161. "referrer": "api.performance.invalid",
  3162. }
  3163. self.do_request(query)
  3164. _, kwargs = mock.call_args
  3165. self.assertEqual(kwargs["referrer"], "api.organization-events-v2")
  3166. @mock.patch("sentry.snuba.discover.query")
  3167. def test_empty_referrer(self, mock):
  3168. mock.return_value = {}
  3169. project = self.create_project()
  3170. data = load_data("transaction", timestamp=before_now(hours=1))
  3171. self.store_event(data=data, project_id=project.id)
  3172. query = {
  3173. "field": ["user"],
  3174. }
  3175. self.do_request(query)
  3176. _, kwargs = mock.call_args
  3177. self.assertEqual(kwargs["referrer"], "api.organization-events-v2")
  3178. def test_limit_number_of_fields(self):
  3179. self.create_project()
  3180. for i in range(1, 25):
  3181. response = self.do_request({"field": ["id"] * i})
  3182. if i <= 20:
  3183. assert response.status_code == 200
  3184. else:
  3185. assert response.status_code == 400
  3186. assert (
  3187. response.data["detail"]
  3188. == "You can view up to 20 fields at a time. Please delete some and try again."
  3189. )
  3190. def test_percentile_function_meta_types(self):
  3191. project = self.create_project()
  3192. data = load_data(
  3193. "transaction",
  3194. timestamp=before_now(minutes=1),
  3195. start_timestamp=before_now(minutes=1, seconds=5),
  3196. )
  3197. self.store_event(data, project_id=project.id)
  3198. query = {
  3199. "field": [
  3200. "transaction",
  3201. "percentile(transaction.duration, 0.95)",
  3202. "percentile(measurements.fp, 0.95)",
  3203. "percentile(measurements.fcp, 0.95)",
  3204. "percentile(measurements.lcp, 0.95)",
  3205. "percentile(measurements.fid, 0.95)",
  3206. "percentile(measurements.ttfb, 0.95)",
  3207. "percentile(measurements.ttfb.requesttime, 0.95)",
  3208. "percentile(measurements.cls, 0.95)",
  3209. "percentile(measurements.foo, 0.95)",
  3210. "percentile(measurements.bar, 0.95)",
  3211. ],
  3212. "query": "",
  3213. "orderby": ["transaction"],
  3214. }
  3215. response = self.do_request(query)
  3216. assert response.status_code == 200, response.content
  3217. meta = response.data["meta"]
  3218. assert meta["percentile_transaction_duration_0_95"] == "duration"
  3219. assert meta["percentile_measurements_fp_0_95"] == "duration"
  3220. assert meta["percentile_measurements_fcp_0_95"] == "duration"
  3221. assert meta["percentile_measurements_lcp_0_95"] == "duration"
  3222. assert meta["percentile_measurements_fid_0_95"] == "duration"
  3223. assert meta["percentile_measurements_ttfb_0_95"] == "duration"
  3224. assert meta["percentile_measurements_ttfb_requesttime_0_95"] == "duration"
  3225. assert meta["percentile_measurements_cls_0_95"] == "number"
  3226. assert meta["percentile_measurements_foo_0_95"] == "number"
  3227. assert meta["percentile_measurements_bar_0_95"] == "number"
  3228. def test_count_at_least_query(self):
  3229. self.store_event(self.transaction_data, self.project.id)
  3230. response = self.do_request({"field": "count_at_least(measurements.fcp, 0)"})
  3231. assert response.status_code == 200
  3232. assert len(response.data["data"]) == 1
  3233. assert response.data["data"][0]["count_at_least_measurements_fcp_0"] == 1
  3234. # a value that's a little bigger than the stored fcp
  3235. fcp = int(self.transaction_data["measurements"]["fcp"]["value"] + 1)
  3236. response = self.do_request({"field": f"count_at_least(measurements.fcp, {fcp})"})
  3237. assert response.status_code == 200
  3238. assert len(response.data["data"]) == 1
  3239. assert response.data["data"][0][f"count_at_least_measurements_fcp_{fcp}"] == 0
  3240. def test_measurements_query(self):
  3241. self.store_event(self.transaction_data, self.project.id)
  3242. query = {
  3243. "field": [
  3244. "measurements.fp",
  3245. "measurements.fcp",
  3246. "measurements.lcp",
  3247. "measurements.fid",
  3248. ]
  3249. }
  3250. response = self.do_request(query)
  3251. assert response.status_code == 200, response.content
  3252. assert len(response.data["data"]) == 1
  3253. for field in query["field"]:
  3254. measure = field.split(".", 1)[1]
  3255. assert (
  3256. response.data["data"][0][field]
  3257. == self.transaction_data["measurements"][measure]["value"]
  3258. )
  3259. query = {
  3260. "field": [
  3261. "measurements.fP",
  3262. "measurements.Fcp",
  3263. "measurements.LcP",
  3264. "measurements.FID",
  3265. ]
  3266. }
  3267. response = self.do_request(query)
  3268. assert response.status_code == 200, response.content
  3269. assert len(response.data["data"]) == 1
  3270. for field in query["field"]:
  3271. measure = field.split(".", 1)[1].lower()
  3272. assert (
  3273. response.data["data"][0][field]
  3274. == self.transaction_data["measurements"][measure]["value"]
  3275. )
  3276. def test_measurements_aggregations(self):
  3277. self.store_event(self.transaction_data, self.project.id)
  3278. # should try all the potential aggregates
  3279. # Skipped tests for stddev and var since sampling one data point
  3280. # results in nan.
  3281. query = {
  3282. "field": [
  3283. "percentile(measurements.fcp, 0.5)",
  3284. "count_unique(measurements.fcp)",
  3285. "min(measurements.fcp)",
  3286. "max(measurements.fcp)",
  3287. "avg(measurements.fcp)",
  3288. "sum(measurements.fcp)",
  3289. ],
  3290. }
  3291. response = self.do_request(query)
  3292. assert response.status_code == 200, response.content
  3293. assert len(response.data["data"]) == 1
  3294. assert (
  3295. response.data["data"][0]["percentile_measurements_fcp_0_5"]
  3296. == self.transaction_data["measurements"]["fcp"]["value"]
  3297. )
  3298. assert response.data["data"][0]["count_unique_measurements_fcp"] == 1
  3299. assert (
  3300. response.data["data"][0]["min_measurements_fcp"]
  3301. == self.transaction_data["measurements"]["fcp"]["value"]
  3302. )
  3303. assert (
  3304. response.data["data"][0]["max_measurements_fcp"]
  3305. == self.transaction_data["measurements"]["fcp"]["value"]
  3306. )
  3307. assert (
  3308. response.data["data"][0]["avg_measurements_fcp"]
  3309. == self.transaction_data["measurements"]["fcp"]["value"]
  3310. )
  3311. assert (
  3312. response.data["data"][0]["sum_measurements_fcp"]
  3313. == self.transaction_data["measurements"]["fcp"]["value"]
  3314. )
  3315. def get_measurement_condition_response(self, query_str, field):
  3316. query = {
  3317. "field": ["transaction", "count()"] + (field if field else []),
  3318. "query": query_str,
  3319. }
  3320. response = self.do_request(query)
  3321. assert response.status_code == 200, response.content
  3322. return response
  3323. def assert_measurement_condition_without_results(self, query_str, field=None):
  3324. response = self.get_measurement_condition_response(query_str, field)
  3325. assert len(response.data["data"]) == 0
  3326. def assert_measurement_condition_with_results(self, query_str, field=None):
  3327. response = self.get_measurement_condition_response(query_str, field)
  3328. assert len(response.data["data"]) == 1
  3329. assert response.data["data"][0]["transaction"] == self.transaction_data["metadata"]["title"]
  3330. assert response.data["data"][0]["count"] == 1
  3331. def test_measurements_conditions(self):
  3332. self.store_event(self.transaction_data, self.project.id)
  3333. fcp = self.transaction_data["measurements"]["fcp"]["value"]
  3334. # equality condition
  3335. # We use json dumps here to ensure precision when converting from float to str
  3336. # This is necessary because equality on floating point values need to be precise
  3337. self.assert_measurement_condition_with_results(f"measurements.fcp:{json.dumps(fcp)}")
  3338. # greater than condition
  3339. self.assert_measurement_condition_with_results(f"measurements.fcp:>{fcp - 1}")
  3340. self.assert_measurement_condition_without_results(f"measurements.fcp:>{fcp + 1}")
  3341. # less than condition
  3342. self.assert_measurement_condition_with_results(f"measurements.fcp:<{fcp + 1}")
  3343. self.assert_measurement_condition_without_results(f"measurements.fcp:<{fcp - 1}")
  3344. # has condition
  3345. self.assert_measurement_condition_with_results("has:measurements.fcp")
  3346. self.assert_measurement_condition_without_results("!has:measurements.fcp")
  3347. def test_measurements_aggregation_conditions(self):
  3348. self.store_event(self.transaction_data, self.project.id)
  3349. fcp = self.transaction_data["measurements"]["fcp"]["value"]
  3350. functions = [
  3351. "percentile(measurements.fcp, 0.5)",
  3352. "min(measurements.fcp)",
  3353. "max(measurements.fcp)",
  3354. "avg(measurements.fcp)",
  3355. "sum(measurements.fcp)",
  3356. ]
  3357. for function in functions:
  3358. self.assert_measurement_condition_with_results(
  3359. f"{function}:>{fcp - 1}", field=[function]
  3360. )
  3361. self.assert_measurement_condition_without_results(
  3362. f"{function}:>{fcp + 1}", field=[function]
  3363. )
  3364. self.assert_measurement_condition_with_results(
  3365. f"{function}:<{fcp + 1}", field=[function]
  3366. )
  3367. self.assert_measurement_condition_without_results(
  3368. f"{function}:<{fcp - 1}", field=[function]
  3369. )
  3370. count_unique = "count_unique(measurements.fcp)"
  3371. self.assert_measurement_condition_with_results(f"{count_unique}:1", field=[count_unique])
  3372. self.assert_measurement_condition_without_results(f"{count_unique}:0", field=[count_unique])
  3373. def test_compare_numeric_aggregate(self):
  3374. self.store_event(self.transaction_data, self.project.id)
  3375. query = {
  3376. "field": [
  3377. "p75(measurements.fcp)",
  3378. "compare_numeric_aggregate(p75_measurements_fcp,greater,0)",
  3379. ],
  3380. }
  3381. response = self.do_request(query)
  3382. assert response.status_code == 200, response.content
  3383. assert len(response.data["data"]) == 1
  3384. assert (
  3385. response.data["data"][0]["compare_numeric_aggregate_p75_measurements_fcp_greater_0"]
  3386. == 1
  3387. )
  3388. query = {
  3389. "field": ["p75()", "compare_numeric_aggregate(p75,equals,0)"],
  3390. }
  3391. response = self.do_request(query)
  3392. assert response.status_code == 200, response.content
  3393. assert len(response.data["data"]) == 1
  3394. assert response.data["data"][0]["compare_numeric_aggregate_p75_equals_0"] == 0
  3395. def test_no_key_transactions(self):
  3396. transactions = [
  3397. "/blah_transaction/",
  3398. "/foo_transaction/",
  3399. "/zoo_transaction/",
  3400. ]
  3401. for transaction in transactions:
  3402. self.transaction_data["transaction"] = transaction
  3403. self.store_event(self.transaction_data, self.project.id)
  3404. query = {
  3405. "project": [self.project.id],
  3406. # use the order by to ensure the result order
  3407. "orderby": "transaction",
  3408. "field": [
  3409. "key_transaction",
  3410. "transaction",
  3411. "transaction.status",
  3412. "project",
  3413. "epm()",
  3414. "failure_rate()",
  3415. "percentile(transaction.duration, 0.95)",
  3416. ],
  3417. }
  3418. response = self.do_request(query)
  3419. assert response.status_code == 200, response.content
  3420. data = response.data["data"]
  3421. assert len(data) == 3
  3422. assert data[0]["key_transaction"] == 0
  3423. assert data[0]["transaction"] == "/blah_transaction/"
  3424. assert data[1]["key_transaction"] == 0
  3425. assert data[1]["transaction"] == "/foo_transaction/"
  3426. assert data[2]["key_transaction"] == 0
  3427. assert data[2]["transaction"] == "/zoo_transaction/"
  3428. def test_key_transactions_orderby(self):
  3429. transactions = ["/blah_transaction/"]
  3430. key_transactions = [
  3431. "/foo_transaction/",
  3432. "/zoo_transaction/",
  3433. ]
  3434. for transaction in transactions:
  3435. self.transaction_data["transaction"] = transaction
  3436. self.store_event(self.transaction_data, self.project.id)
  3437. for transaction in key_transactions:
  3438. self.transaction_data["transaction"] = transaction
  3439. self.store_event(self.transaction_data, self.project.id)
  3440. KeyTransaction.objects.create(
  3441. owner=self.user,
  3442. organization=self.organization,
  3443. transaction=transaction,
  3444. project=self.project,
  3445. )
  3446. query = {
  3447. "project": [self.project.id],
  3448. "field": [
  3449. "key_transaction",
  3450. "transaction",
  3451. "transaction.status",
  3452. "project",
  3453. "epm()",
  3454. "failure_rate()",
  3455. "percentile(transaction.duration, 0.95)",
  3456. ],
  3457. }
  3458. # test ascending order
  3459. query["orderby"] = ["key_transaction", "transaction"]
  3460. response = self.do_request(query)
  3461. assert response.status_code == 200, response.content
  3462. data = response.data["data"]
  3463. assert len(data) == 3
  3464. assert data[0]["key_transaction"] == 0
  3465. assert data[0]["transaction"] == "/blah_transaction/"
  3466. assert data[1]["key_transaction"] == 1
  3467. assert data[1]["transaction"] == "/foo_transaction/"
  3468. assert data[2]["key_transaction"] == 1
  3469. assert data[2]["transaction"] == "/zoo_transaction/"
  3470. # test descending order
  3471. query["orderby"] = ["-key_transaction", "-transaction"]
  3472. response = self.do_request(query)
  3473. assert response.status_code == 200, response.content
  3474. data = response.data["data"]
  3475. assert len(data) == 3
  3476. assert data[0]["key_transaction"] == 1
  3477. assert data[0]["transaction"] == "/zoo_transaction/"
  3478. assert data[1]["key_transaction"] == 1
  3479. assert data[1]["transaction"] == "/foo_transaction/"
  3480. assert data[2]["key_transaction"] == 0
  3481. assert data[2]["transaction"] == "/blah_transaction/"
  3482. def test_key_transactions_query(self):
  3483. transactions = ["/blah_transaction/"]
  3484. key_transactions = [
  3485. "/foo_transaction/",
  3486. "/zoo_transaction/",
  3487. ]
  3488. for transaction in transactions:
  3489. self.transaction_data["transaction"] = transaction
  3490. self.store_event(self.transaction_data, self.project.id)
  3491. for transaction in key_transactions:
  3492. self.transaction_data["transaction"] = transaction
  3493. self.store_event(self.transaction_data, self.project.id)
  3494. KeyTransaction.objects.create(
  3495. owner=self.user,
  3496. organization=self.organization,
  3497. transaction=transaction,
  3498. project=self.project,
  3499. )
  3500. query = {
  3501. "project": [self.project.id],
  3502. "orderby": "transaction",
  3503. "field": [
  3504. "key_transaction",
  3505. "transaction",
  3506. "transaction.status",
  3507. "project",
  3508. "epm()",
  3509. "failure_rate()",
  3510. "percentile(transaction.duration, 0.95)",
  3511. ],
  3512. }
  3513. # key transactions
  3514. query["query"] = "has:key_transaction"
  3515. response = self.do_request(query)
  3516. assert response.status_code == 200, response.content
  3517. data = response.data["data"]
  3518. assert len(data) == 2
  3519. assert data[0]["key_transaction"] == 1
  3520. assert data[0]["transaction"] == "/foo_transaction/"
  3521. assert data[1]["key_transaction"] == 1
  3522. assert data[1]["transaction"] == "/zoo_transaction/"
  3523. # key transactions
  3524. query["query"] = "key_transaction:true"
  3525. response = self.do_request(query)
  3526. assert response.status_code == 200, response.content
  3527. data = response.data["data"]
  3528. assert len(data) == 2
  3529. assert data[0]["key_transaction"] == 1
  3530. assert data[0]["transaction"] == "/foo_transaction/"
  3531. assert data[1]["key_transaction"] == 1
  3532. assert data[1]["transaction"] == "/zoo_transaction/"
  3533. # not key transactions
  3534. query["query"] = "!has:key_transaction"
  3535. response = self.do_request(query)
  3536. assert response.status_code == 200, response.content
  3537. data = response.data["data"]
  3538. assert len(data) == 1
  3539. assert data[0]["key_transaction"] == 0
  3540. assert data[0]["transaction"] == "/blah_transaction/"
  3541. # not key transactions
  3542. query["query"] = "key_transaction:false"
  3543. response = self.do_request(query)
  3544. assert response.status_code == 200, response.content
  3545. data = response.data["data"]
  3546. assert len(data) == 1
  3547. assert data[0]["key_transaction"] == 0
  3548. assert data[0]["transaction"] == "/blah_transaction/"
  3549. def test_no_team_key_transactions(self):
  3550. transactions = [
  3551. "/blah_transaction/",
  3552. "/foo_transaction/",
  3553. "/zoo_transaction/",
  3554. ]
  3555. for transaction in transactions:
  3556. self.transaction_data["transaction"] = transaction
  3557. self.store_event(self.transaction_data, self.project.id)
  3558. query = {
  3559. "team": "myteams",
  3560. "project": [self.project.id],
  3561. # use the order by to ensure the result order
  3562. "orderby": "transaction",
  3563. "field": [
  3564. "team_key_transaction",
  3565. "transaction",
  3566. "transaction.status",
  3567. "project",
  3568. "epm()",
  3569. "failure_rate()",
  3570. "percentile(transaction.duration, 0.95)",
  3571. ],
  3572. }
  3573. response = self.do_request(query)
  3574. assert response.status_code == 200, response.content
  3575. data = response.data["data"]
  3576. assert len(data) == 3
  3577. assert data[0]["team_key_transaction"] == 0
  3578. assert data[0]["transaction"] == "/blah_transaction/"
  3579. assert data[1]["team_key_transaction"] == 0
  3580. assert data[1]["transaction"] == "/foo_transaction/"
  3581. assert data[2]["team_key_transaction"] == 0
  3582. assert data[2]["transaction"] == "/zoo_transaction/"
  3583. def test_team_key_transactions_my_teams(self):
  3584. team1 = self.create_team(organization=self.organization, name="Team A")
  3585. self.create_team_membership(team1, user=self.user)
  3586. self.project.add_team(team1)
  3587. team2 = self.create_team(organization=self.organization, name="Team B")
  3588. self.project.add_team(team2)
  3589. transactions = ["/blah_transaction/"]
  3590. key_transactions = [
  3591. (team1, "/foo_transaction/"),
  3592. (team2, "/zoo_transaction/"),
  3593. ]
  3594. for transaction in transactions:
  3595. self.transaction_data["transaction"] = transaction
  3596. self.store_event(self.transaction_data, self.project.id)
  3597. for team, transaction in key_transactions:
  3598. self.transaction_data["transaction"] = transaction
  3599. self.store_event(self.transaction_data, self.project.id)
  3600. TeamKeyTransaction.objects.create(
  3601. organization=self.organization,
  3602. transaction=transaction,
  3603. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  3604. )
  3605. query = {
  3606. "team": "myteams",
  3607. "project": [self.project.id],
  3608. "field": [
  3609. "team_key_transaction",
  3610. "transaction",
  3611. "transaction.status",
  3612. "project",
  3613. "epm()",
  3614. "failure_rate()",
  3615. "percentile(transaction.duration, 0.95)",
  3616. ],
  3617. }
  3618. query["orderby"] = ["team_key_transaction", "transaction"]
  3619. response = self.do_request(query)
  3620. assert response.status_code == 200, response.content
  3621. data = response.data["data"]
  3622. assert len(data) == 3
  3623. assert data[0]["team_key_transaction"] == 0
  3624. assert data[0]["transaction"] == "/blah_transaction/"
  3625. assert data[1]["team_key_transaction"] == 0
  3626. assert data[1]["transaction"] == "/zoo_transaction/"
  3627. assert data[2]["team_key_transaction"] == 1
  3628. assert data[2]["transaction"] == "/foo_transaction/"
  3629. # not specifying any teams should use my teams
  3630. query = {
  3631. "project": [self.project.id],
  3632. "field": [
  3633. "team_key_transaction",
  3634. "transaction",
  3635. "transaction.status",
  3636. "project",
  3637. "epm()",
  3638. "failure_rate()",
  3639. "percentile(transaction.duration, 0.95)",
  3640. ],
  3641. }
  3642. query["orderby"] = ["team_key_transaction", "transaction"]
  3643. response = self.do_request(query)
  3644. assert response.status_code == 200, response.content
  3645. data = response.data["data"]
  3646. assert len(data) == 3
  3647. assert data[0]["team_key_transaction"] == 0
  3648. assert data[0]["transaction"] == "/blah_transaction/"
  3649. assert data[1]["team_key_transaction"] == 0
  3650. assert data[1]["transaction"] == "/zoo_transaction/"
  3651. assert data[2]["team_key_transaction"] == 1
  3652. assert data[2]["transaction"] == "/foo_transaction/"
  3653. def test_team_key_transactions_orderby(self):
  3654. team1 = self.create_team(organization=self.organization, name="Team A")
  3655. team2 = self.create_team(organization=self.organization, name="Team B")
  3656. transactions = ["/blah_transaction/"]
  3657. key_transactions = [
  3658. (team1, "/foo_transaction/"),
  3659. (team2, "/zoo_transaction/"),
  3660. ]
  3661. for transaction in transactions:
  3662. self.transaction_data["transaction"] = transaction
  3663. self.store_event(self.transaction_data, self.project.id)
  3664. for team, transaction in key_transactions:
  3665. self.create_team_membership(team, user=self.user)
  3666. self.project.add_team(team)
  3667. self.transaction_data["transaction"] = transaction
  3668. self.store_event(self.transaction_data, self.project.id)
  3669. TeamKeyTransaction.objects.create(
  3670. organization=self.organization,
  3671. transaction=transaction,
  3672. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  3673. )
  3674. query = {
  3675. "team": "myteams",
  3676. "project": [self.project.id],
  3677. "field": [
  3678. "team_key_transaction",
  3679. "transaction",
  3680. "transaction.status",
  3681. "project",
  3682. "epm()",
  3683. "failure_rate()",
  3684. "percentile(transaction.duration, 0.95)",
  3685. ],
  3686. }
  3687. # test ascending order
  3688. query["orderby"] = ["team_key_transaction", "transaction"]
  3689. response = self.do_request(query)
  3690. assert response.status_code == 200, response.content
  3691. data = response.data["data"]
  3692. assert len(data) == 3
  3693. assert data[0]["team_key_transaction"] == 0
  3694. assert data[0]["transaction"] == "/blah_transaction/"
  3695. assert data[1]["team_key_transaction"] == 1
  3696. assert data[1]["transaction"] == "/foo_transaction/"
  3697. assert data[2]["team_key_transaction"] == 1
  3698. assert data[2]["transaction"] == "/zoo_transaction/"
  3699. # test descending order
  3700. query["orderby"] = ["-team_key_transaction", "-transaction"]
  3701. response = self.do_request(query)
  3702. assert response.status_code == 200, response.content
  3703. data = response.data["data"]
  3704. assert len(data) == 3
  3705. assert data[0]["team_key_transaction"] == 1
  3706. assert data[0]["transaction"] == "/zoo_transaction/"
  3707. assert data[1]["team_key_transaction"] == 1
  3708. assert data[1]["transaction"] == "/foo_transaction/"
  3709. assert data[2]["team_key_transaction"] == 0
  3710. assert data[2]["transaction"] == "/blah_transaction/"
  3711. def test_team_key_transactions_query(self):
  3712. team1 = self.create_team(organization=self.organization, name="Team A")
  3713. team2 = self.create_team(organization=self.organization, name="Team B")
  3714. transactions = ["/blah_transaction/"]
  3715. key_transactions = [
  3716. (team1, "/foo_transaction/"),
  3717. (team2, "/zoo_transaction/"),
  3718. ]
  3719. for transaction in transactions:
  3720. self.transaction_data["transaction"] = transaction
  3721. self.store_event(self.transaction_data, self.project.id)
  3722. for team, transaction in key_transactions:
  3723. self.create_team_membership(team, user=self.user)
  3724. self.project.add_team(team)
  3725. self.transaction_data["transaction"] = transaction
  3726. self.store_event(self.transaction_data, self.project.id)
  3727. TeamKeyTransaction.objects.create(
  3728. organization=self.organization,
  3729. project_team=ProjectTeam.objects.get(
  3730. project=self.project,
  3731. team=team,
  3732. ),
  3733. transaction=transaction,
  3734. )
  3735. query = {
  3736. "team": "myteams",
  3737. "project": [self.project.id],
  3738. # use the order by to ensure the result order
  3739. "orderby": "transaction",
  3740. "field": [
  3741. "team_key_transaction",
  3742. "transaction",
  3743. "transaction.status",
  3744. "project",
  3745. "epm()",
  3746. "failure_rate()",
  3747. "percentile(transaction.duration, 0.95)",
  3748. ],
  3749. }
  3750. # key transactions
  3751. query["query"] = "has:team_key_transaction"
  3752. response = self.do_request(query)
  3753. assert response.status_code == 200, response.content
  3754. data = response.data["data"]
  3755. assert len(data) == 2
  3756. assert data[0]["team_key_transaction"] == 1
  3757. assert data[0]["transaction"] == "/foo_transaction/"
  3758. assert data[1]["team_key_transaction"] == 1
  3759. assert data[1]["transaction"] == "/zoo_transaction/"
  3760. # key transactions
  3761. query["query"] = "team_key_transaction:true"
  3762. response = self.do_request(query)
  3763. assert response.status_code == 200, response.content
  3764. data = response.data["data"]
  3765. assert len(data) == 2
  3766. assert data[0]["team_key_transaction"] == 1
  3767. assert data[0]["transaction"] == "/foo_transaction/"
  3768. assert data[1]["team_key_transaction"] == 1
  3769. assert data[1]["transaction"] == "/zoo_transaction/"
  3770. # not key transactions
  3771. query["query"] = "!has:team_key_transaction"
  3772. response = self.do_request(query)
  3773. assert response.status_code == 200, response.content
  3774. data = response.data["data"]
  3775. assert len(data) == 1
  3776. assert data[0]["team_key_transaction"] == 0
  3777. assert data[0]["transaction"] == "/blah_transaction/"
  3778. # not key transactions
  3779. query["query"] = "team_key_transaction:false"
  3780. response = self.do_request(query)
  3781. assert response.status_code == 200, response.content
  3782. data = response.data["data"]
  3783. assert len(data) == 1
  3784. assert data[0]["team_key_transaction"] == 0
  3785. assert data[0]["transaction"] == "/blah_transaction/"
  3786. def test_too_many_team_key_transactions(self):
  3787. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  3788. with mock.patch(
  3789. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  3790. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  3791. ):
  3792. team = self.create_team(organization=self.organization, name="Team A")
  3793. self.create_team_membership(team, user=self.user)
  3794. self.project.add_team(team)
  3795. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  3796. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  3797. transaction = f"transaction-{team.id}-{i}"
  3798. self.transaction_data["transaction"] = transaction
  3799. self.store_event(self.transaction_data, self.project.id)
  3800. TeamKeyTransaction.objects.bulk_create(
  3801. [
  3802. TeamKeyTransaction(
  3803. organization=self.organization,
  3804. project_team=project_team,
  3805. transaction=f"transaction-{team.id}-{i}",
  3806. )
  3807. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  3808. ]
  3809. )
  3810. query = {
  3811. "team": "myteams",
  3812. "project": [self.project.id],
  3813. "orderby": "transaction",
  3814. "field": [
  3815. "team_key_transaction",
  3816. "transaction",
  3817. "transaction.status",
  3818. "project",
  3819. "epm()",
  3820. "failure_rate()",
  3821. "percentile(transaction.duration, 0.95)",
  3822. ],
  3823. }
  3824. response = self.do_request(query)
  3825. assert response.status_code == 200, response.content
  3826. data = response.data["data"]
  3827. assert len(data) == 2
  3828. assert (
  3829. sum(row["team_key_transaction"] for row in data)
  3830. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  3831. )
  3832. def test_no_pagination_param(self):
  3833. self.store_event(
  3834. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  3835. project_id=self.project.id,
  3836. )
  3837. query = {"field": ["id", "project.id"], "project": [self.project.id], "noPagination": True}
  3838. response = self.do_request(query)
  3839. assert response.status_code == 200
  3840. assert len(response.data["data"]) == 1
  3841. assert "Link" not in response
  3842. def test_nan_result(self):
  3843. query = {"field": ["apdex(300)"], "project": [self.project.id], "query": f"id:{'0' * 32}"}
  3844. response = self.do_request(query)
  3845. assert response.status_code == 200
  3846. assert len(response.data["data"]) == 1
  3847. assert response.data["data"][0]["apdex_300"] == 0
  3848. def test_equation_simple(self):
  3849. event_data = load_data("transaction", timestamp=before_now(minutes=1))
  3850. event_data["breakdowns"]["span_ops"]["ops.http"]["value"] = 1500
  3851. self.store_event(data=event_data, project_id=self.project.id)
  3852. query = {
  3853. "field": ["spans.http", "equation|spans.http / 3"],
  3854. "project": [self.project.id],
  3855. "query": "event.type:transaction",
  3856. }
  3857. response = self.do_request(
  3858. query,
  3859. {
  3860. "organizations:discover-basic": True,
  3861. "organizations:discover-arithmetic": True,
  3862. },
  3863. )
  3864. assert response.status_code == 200
  3865. assert len(response.data["data"]) == 1
  3866. assert (
  3867. response.data["data"][0]["equation[0]"]
  3868. == event_data["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
  3869. )
  3870. def test_equation_operation_limit(self):
  3871. query = {
  3872. "field": ["spans.http", f"equation|spans.http{' * 2' * 11}"],
  3873. "project": [self.project.id],
  3874. "query": "event.type:transaction",
  3875. }
  3876. response = self.do_request(
  3877. query,
  3878. {
  3879. "organizations:discover-basic": True,
  3880. "organizations:discover-arithmetic": True,
  3881. },
  3882. )
  3883. assert response.status_code == 400
  3884. @mock.patch("sentry.api.bases.organization_events.MAX_FIELDS", 2)
  3885. def test_equation_field_limit(self):
  3886. query = {
  3887. "field": ["spans.http", "transaction.duration", "equation|5 * 2"],
  3888. "project": [self.project.id],
  3889. "query": "event.type:transaction",
  3890. }
  3891. response = self.do_request(
  3892. query,
  3893. {
  3894. "organizations:discover-basic": True,
  3895. "organizations:discover-arithmetic": True,
  3896. },
  3897. )
  3898. assert response.status_code == 400
  3899. def test_count_if(self):
  3900. for i in range(5):
  3901. data = load_data(
  3902. "transaction",
  3903. timestamp=before_now(minutes=(1 + i)),
  3904. start_timestamp=before_now(minutes=(1 + i), milliseconds=100 if i < 3 else 200),
  3905. )
  3906. data["tags"] = {"sub_customer.is-Enterprise-42": "yes" if i == 0 else "no"}
  3907. self.store_event(data, project_id=self.project.id)
  3908. query = {
  3909. "field": [
  3910. "count_if(transaction.duration, less, 150)",
  3911. "count_if(transaction.duration, greater, 150)",
  3912. "count_if(sub_customer.is-Enterprise-42, equals, yes)",
  3913. "count_if(sub_customer.is-Enterprise-42, notEquals, yes)",
  3914. ],
  3915. "project": [self.project.id],
  3916. }
  3917. response = self.do_request(query)
  3918. assert response.status_code == 200
  3919. assert len(response.data["data"]) == 1
  3920. assert response.data["data"][0]["count_if_transaction_duration_less_150"] == 3
  3921. assert response.data["data"][0]["count_if_transaction_duration_greater_150"] == 2
  3922. assert response.data["data"][0]["count_if_sub_customer_is_Enterprise_42_equals_yes"] == 1
  3923. assert response.data["data"][0]["count_if_sub_customer_is_Enterprise_42_notEquals_yes"] == 4
  3924. def test_count_if_filter(self):
  3925. for i in range(5):
  3926. data = load_data(
  3927. "transaction",
  3928. timestamp=before_now(minutes=(1 + i)),
  3929. start_timestamp=before_now(minutes=(1 + i), milliseconds=100 if i < 3 else 200),
  3930. )
  3931. data["tags"] = {"sub_customer.is-Enterprise-42": "yes" if i == 0 else "no"}
  3932. self.store_event(data, project_id=self.project.id)
  3933. query = {
  3934. "field": [
  3935. "count_if(transaction.duration, less, 150)",
  3936. ],
  3937. "query": "count_if(transaction.duration, less, 150):>2",
  3938. "project": [self.project.id],
  3939. }
  3940. response = self.do_request(query)
  3941. assert response.status_code == 200
  3942. assert len(response.data["data"]) == 1
  3943. assert response.data["data"][0]["count_if_transaction_duration_less_150"] == 3
  3944. query = {
  3945. "field": [
  3946. "count_if(transaction.duration, less, 150)",
  3947. ],
  3948. "query": "count_if(transaction.duration, less, 150):<2",
  3949. "project": [self.project.id],
  3950. }
  3951. response = self.do_request(query)
  3952. assert response.status_code == 200
  3953. assert len(response.data["data"]) == 0
  3954. def test_filters_with_escaped_asterisk(self):
  3955. data = load_data("transaction", timestamp=before_now(minutes=1))
  3956. data["transaction"] = r"/:a*/:b-:c(\d\.\e+)"
  3957. self.store_event(data, project_id=self.project.id)
  3958. query = {
  3959. "field": ["transaction", "transaction.duration"],
  3960. # make sure to escape the asterisk so it's not treated as a wildcard
  3961. "query": r'transaction:"/:a\*/:b-:c(\d\.\e+)"',
  3962. "project": [self.project.id],
  3963. }
  3964. response = self.do_request(query)
  3965. assert response.status_code == 200
  3966. assert len(response.data["data"]) == 1
  3967. def test_filters_with_back_slashes(self):
  3968. data = load_data("transaction", timestamp=before_now(minutes=1))
  3969. data["transaction"] = r"a\b\c@d"
  3970. self.store_event(data, project_id=self.project.id)
  3971. query = {
  3972. "field": ["transaction", "transaction.duration"],
  3973. "query": r'transaction:"a\b\c@d"',
  3974. "project": [self.project.id],
  3975. }
  3976. response = self.do_request(query)
  3977. assert response.status_code == 200
  3978. assert len(response.data["data"]) == 1