test_organization_events_v2.py 167 KB

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