test_organization_events_v2.py 125 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253
  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
  6. from sentry.models import ApiKey
  7. from sentry.testutils import APITestCase, SnubaTestCase
  8. from sentry.testutils.helpers import parse_link_header
  9. from sentry.testutils.helpers.datetime import before_now, iso_format
  10. from sentry.utils import json
  11. from sentry.utils.compat import mock, zip
  12. from sentry.utils.samples import load_data
  13. from sentry.utils.snuba import QueryExecutionError, QueryIllegalTypeOfArgument, RateLimitExceeded
  14. class OrganizationEventsV2EndpointTest(APITestCase, SnubaTestCase):
  15. def setUp(self):
  16. super().setUp()
  17. self.min_ago = iso_format(before_now(minutes=1))
  18. self.two_min_ago = iso_format(before_now(minutes=2))
  19. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  20. def do_request(self, query, features=None):
  21. if features is None:
  22. features = {"organizations:discover-basic": True}
  23. self.login_as(user=self.user)
  24. url = reverse(
  25. "sentry-api-0-organization-eventsv2",
  26. kwargs={"organization_slug": self.organization.slug},
  27. )
  28. with self.feature(features):
  29. return self.client.get(url, query, format="json")
  30. def test_no_projects(self):
  31. response = self.do_request({})
  32. assert response.status_code == 200, response.content
  33. assert len(response.data) == 0
  34. def test_api_key_request(self):
  35. project = self.create_project()
  36. self.store_event(
  37. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  38. project_id=project.id,
  39. )
  40. # Project ID cannot be inffered when using an org API key, so that must
  41. # be passed in the parameters
  42. api_key = ApiKey.objects.create(organization=self.organization, scope_list=["org:read"])
  43. query = {"field": ["project.name", "environment"], "project": [project.id]}
  44. url = reverse(
  45. "sentry-api-0-organization-eventsv2",
  46. kwargs={"organization_slug": self.organization.slug},
  47. )
  48. response = self.client.get(
  49. url,
  50. query,
  51. format="json",
  52. HTTP_AUTHORIZATION=b"Basic " + b64encode(f"{api_key.key}:".encode("utf-8")),
  53. )
  54. assert response.status_code == 200, response.content
  55. assert len(response.data["data"]) == 1
  56. assert response.data["data"][0]["project.name"] == project.slug
  57. def test_performance_view_feature(self):
  58. self.store_event(
  59. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  60. project_id=self.project.id,
  61. )
  62. query = {"field": ["id", "project.id"], "project": [self.project.id]}
  63. response = self.do_request(query)
  64. assert response.status_code == 200
  65. assert len(response.data["data"]) == 1
  66. def test_multi_project_feature_gate_rejection(self):
  67. team = self.create_team(organization=self.organization, members=[self.user])
  68. project = self.create_project(organization=self.organization, teams=[team])
  69. project2 = self.create_project(organization=self.organization, teams=[team])
  70. self.store_event(
  71. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  72. project_id=project.id,
  73. )
  74. self.store_event(
  75. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  76. project_id=project2.id,
  77. )
  78. query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
  79. response = self.do_request(query)
  80. assert response.status_code == 400
  81. assert "events from multiple projects" in response.data["detail"]
  82. def test_invalid_search_terms(self):
  83. project = self.create_project()
  84. self.store_event(
  85. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  86. project_id=project.id,
  87. )
  88. query = {"field": ["id"], "query": "hi \n there"}
  89. response = self.do_request(query)
  90. assert response.status_code == 400, response.content
  91. assert (
  92. response.data["detail"]
  93. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  94. )
  95. @mock.patch("sentry.snuba.discover.raw_query")
  96. def test_handling_snuba_errors(self, mock_query):
  97. mock_query.side_effect = RateLimitExceeded("test")
  98. project = self.create_project()
  99. self.store_event(
  100. data={"event_id": "a" * 32, "message": "how to make fast"}, project_id=project.id
  101. )
  102. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  103. response = self.do_request(query)
  104. assert response.status_code == 400, response.content
  105. assert (
  106. response.data["detail"]
  107. == "Query timeout. Please try again. If the problem persists try a smaller date range or fewer projects."
  108. )
  109. mock_query.side_effect = QueryExecutionError("test")
  110. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  111. response = self.do_request(query)
  112. assert response.status_code == 500, response.content
  113. assert response.data["detail"] == "Internal error. Your query failed to run."
  114. mock_query.side_effect = QueryIllegalTypeOfArgument("test")
  115. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  116. response = self.do_request(query)
  117. assert response.status_code == 400, response.content
  118. assert response.data["detail"] == "Invalid query. Argument to function is wrong type."
  119. def test_out_of_retention(self):
  120. self.create_project()
  121. with self.options({"system.event-retention-days": 10}):
  122. query = {
  123. "field": ["id", "timestamp"],
  124. "orderby": ["-timestamp", "-id"],
  125. "start": iso_format(before_now(days=20)),
  126. "end": iso_format(before_now(days=15)),
  127. }
  128. response = self.do_request(query)
  129. assert response.status_code == 400, response.content
  130. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  131. def test_raw_data(self):
  132. project = self.create_project()
  133. self.store_event(
  134. data={
  135. "event_id": "a" * 32,
  136. "environment": "staging",
  137. "timestamp": self.two_min_ago,
  138. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  139. },
  140. project_id=project.id,
  141. )
  142. self.store_event(
  143. data={
  144. "event_id": "b" * 32,
  145. "environment": "staging",
  146. "timestamp": self.min_ago,
  147. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  148. },
  149. project_id=project.id,
  150. )
  151. query = {
  152. "field": ["id", "project.id", "user.email", "user.ip", "timestamp"],
  153. "orderby": "-timestamp",
  154. }
  155. response = self.do_request(query)
  156. assert response.status_code == 200, response.content
  157. data = response.data["data"]
  158. assert len(data) == 2
  159. assert data[0]["id"] == "b" * 32
  160. assert data[0]["project.id"] == project.id
  161. assert data[0]["user.email"] == "foo@example.com"
  162. assert "project.name" not in data[0], "project.id does not auto select name"
  163. assert "project" not in data[0]
  164. meta = response.data["meta"]
  165. assert meta["id"] == "string"
  166. assert meta["user.email"] == "string"
  167. assert meta["user.ip"] == "string"
  168. assert meta["timestamp"] == "date"
  169. def test_project_name(self):
  170. project = self.create_project()
  171. self.store_event(
  172. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  173. project_id=project.id,
  174. )
  175. query = {"field": ["project.name", "environment"]}
  176. response = self.do_request(query)
  177. assert response.status_code == 200, response.content
  178. assert len(response.data["data"]) == 1
  179. assert response.data["data"][0]["project.name"] == project.slug
  180. assert "project.id" not in response.data["data"][0]
  181. assert response.data["data"][0]["environment"] == "staging"
  182. def test_project_without_name(self):
  183. project = self.create_project()
  184. self.store_event(
  185. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  186. project_id=project.id,
  187. )
  188. query = {"field": ["project", "environment"]}
  189. response = self.do_request(query)
  190. assert response.status_code == 200, response.content
  191. assert len(response.data["data"]) == 1
  192. assert response.data["data"][0]["project"] == project.slug
  193. assert response.data["meta"]["project"] == "string"
  194. assert "project.id" not in response.data["data"][0]
  195. assert response.data["data"][0]["environment"] == "staging"
  196. def test_project_in_query(self):
  197. project = self.create_project()
  198. self.store_event(
  199. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  200. project_id=project.id,
  201. )
  202. query = {
  203. "field": ["project", "count()"],
  204. "query": 'project:"%s"' % project.slug,
  205. "statsPeriod": "14d",
  206. }
  207. response = self.do_request(query)
  208. assert response.status_code == 200, response.content
  209. assert len(response.data["data"]) == 1
  210. assert response.data["data"][0]["project"] == project.slug
  211. assert "project.id" not in response.data["data"][0]
  212. def test_project_in_query_not_in_header(self):
  213. project = self.create_project()
  214. other_project = self.create_project()
  215. self.store_event(
  216. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  217. project_id=project.id,
  218. )
  219. query = {
  220. "field": ["project", "count()"],
  221. "query": 'project:"%s"' % project.slug,
  222. "statsPeriod": "14d",
  223. "project": other_project.id,
  224. }
  225. response = self.do_request(query)
  226. assert response.status_code == 400, response.content
  227. assert (
  228. response.data["detail"]
  229. == f"Invalid query. Project(s) {project.slug} do not exist or are not actively selected."
  230. )
  231. def test_project_in_query_does_not_exist(self):
  232. project = self.create_project()
  233. self.store_event(
  234. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  235. project_id=project.id,
  236. )
  237. query = {"field": ["project", "count()"], "query": "project:morty", "statsPeriod": "14d"}
  238. response = self.do_request(query)
  239. assert response.status_code == 400, response.content
  240. assert (
  241. response.data["detail"]
  242. == "Invalid query. Project(s) morty do not exist or are not actively selected."
  243. )
  244. def test_not_project_in_query_but_in_header(self):
  245. team = self.create_team(organization=self.organization, members=[self.user])
  246. project = self.create_project(organization=self.organization, teams=[team])
  247. project2 = self.create_project(organization=self.organization, teams=[team])
  248. self.store_event(
  249. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  250. project_id=project.id,
  251. )
  252. self.store_event(
  253. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  254. project_id=project2.id,
  255. )
  256. query = {
  257. "field": ["id", "project.id"],
  258. "project": [project.id],
  259. "query": f"!project:{project2.slug}",
  260. }
  261. response = self.do_request(query)
  262. assert response.status_code == 200
  263. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  264. def test_not_project_in_query_with_all_projects(self):
  265. team = self.create_team(organization=self.organization, members=[self.user])
  266. project = self.create_project(organization=self.organization, teams=[team])
  267. project2 = self.create_project(organization=self.organization, teams=[team])
  268. self.store_event(
  269. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  270. project_id=project.id,
  271. )
  272. self.store_event(
  273. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  274. project_id=project2.id,
  275. )
  276. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  277. query = {
  278. "field": ["id", "project.id"],
  279. "project": [-1],
  280. "query": f"!project:{project2.slug}",
  281. }
  282. response = self.do_request(query, features=features)
  283. assert response.status_code == 200
  284. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  285. def test_project_condition_used_for_automatic_filters(self):
  286. project = self.create_project()
  287. self.store_event(
  288. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  289. project_id=project.id,
  290. )
  291. query = {
  292. "field": ["project", "count()"],
  293. "query": 'project:"%s"' % project.slug,
  294. "statsPeriod": "14d",
  295. }
  296. response = self.do_request(query)
  297. assert response.status_code == 200, response.content
  298. assert len(response.data["data"]) == 1
  299. assert response.data["data"][0]["project"] == project.slug
  300. assert "project.id" not in response.data["data"][0]
  301. def test_auto_insert_project_name_when_event_id_present(self):
  302. project = self.create_project()
  303. self.store_event(
  304. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  305. project_id=project.id,
  306. )
  307. query = {"field": ["id"], "statsPeriod": "1h"}
  308. response = self.do_request(query)
  309. assert response.status_code == 200, response.content
  310. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32}]
  311. def test_auto_insert_project_name_when_event_id_present_with_aggregate(self):
  312. project = self.create_project()
  313. self.store_event(
  314. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  315. project_id=project.id,
  316. )
  317. query = {"field": ["id", "count()"], "statsPeriod": "1h"}
  318. response = self.do_request(query)
  319. assert response.status_code == 200, response.content
  320. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32, "count": 1}]
  321. def test_user_search(self):
  322. project = self.create_project()
  323. data = load_data("transaction", timestamp=before_now(minutes=1))
  324. data["user"] = {
  325. "email": "foo@example.com",
  326. "id": "123",
  327. "ip_address": "127.0.0.1",
  328. "username": "foo",
  329. }
  330. self.store_event(data, project_id=project.id)
  331. fields = {
  332. "email": "user.email",
  333. "id": "user.id",
  334. "ip_address": "user.ip",
  335. "username": "user.username",
  336. }
  337. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  338. for key, value in data["user"].items():
  339. field = fields[key]
  340. query = {
  341. "field": ["project", "user"],
  342. "query": f"{field}:{value}",
  343. "statsPeriod": "14d",
  344. }
  345. response = self.do_request(query, features=features)
  346. assert response.status_code == 200, response.content
  347. assert len(response.data["data"]) == 1
  348. assert response.data["data"][0]["project"] == project.slug
  349. assert response.data["data"][0]["user"] == "id:123"
  350. def test_has_user(self):
  351. project = self.create_project()
  352. data = load_data("transaction", timestamp=before_now(minutes=1))
  353. self.store_event(data, project_id=project.id)
  354. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  355. for value in data["user"].values():
  356. query = {"field": ["project", "user"], "query": "has:user", "statsPeriod": "14d"}
  357. response = self.do_request(query, features=features)
  358. assert response.status_code == 200, response.content
  359. assert len(response.data["data"]) == 1
  360. assert response.data["data"][0]["user"] == "ip:{}".format(data["user"]["ip_address"])
  361. def test_has_issue(self):
  362. project = self.create_project()
  363. event = self.store_event(
  364. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  365. )
  366. data = load_data("transaction", timestamp=before_now(minutes=1))
  367. self.store_event(data, project_id=project.id)
  368. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  369. # should only show 1 event of type default
  370. query = {"field": ["project", "issue"], "query": "has:issue", "statsPeriod": "14d"}
  371. response = self.do_request(query, features=features)
  372. assert response.status_code == 200, response.content
  373. assert len(response.data["data"]) == 1
  374. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  375. # should only show 1 event of type default
  376. query = {
  377. "field": ["project", "issue"],
  378. "query": "event.type:default has:issue",
  379. "statsPeriod": "14d",
  380. }
  381. response = self.do_request(query, features=features)
  382. assert response.status_code == 200, response.content
  383. assert len(response.data["data"]) == 1
  384. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  385. # should show no results because no the default event has an issue
  386. query = {
  387. "field": ["project", "issue"],
  388. "query": "event.type:default !has:issue",
  389. "statsPeriod": "14d",
  390. }
  391. response = self.do_request(query, features=features)
  392. assert response.status_code == 200, response.content
  393. assert len(response.data["data"]) == 0
  394. # should show no results because no transactions have issues
  395. query = {
  396. "field": ["project", "issue"],
  397. "query": "event.type:transaction has:issue",
  398. "statsPeriod": "14d",
  399. }
  400. response = self.do_request(query, features=features)
  401. assert response.status_code == 200, response.content
  402. assert len(response.data["data"]) == 0
  403. # should only show 1 event of type transaction since they dont have issues
  404. query = {
  405. "field": ["project", "issue"],
  406. "query": "event.type:transaction !has:issue",
  407. "statsPeriod": "14d",
  408. }
  409. response = self.do_request(query, features=features)
  410. assert response.status_code == 200, response.content
  411. assert len(response.data["data"]) == 1
  412. assert response.data["data"][0]["issue"] == "unknown"
  413. @pytest.mark.skip("Cannot look up group_id of transaction events")
  414. def test_unknown_issue(self):
  415. project = self.create_project()
  416. event = self.store_event(
  417. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  418. )
  419. data = load_data("transaction", timestamp=before_now(minutes=1))
  420. self.store_event(data, project_id=project.id)
  421. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  422. query = {"field": ["project", "issue"], "query": "issue:unknown", "statsPeriod": "14d"}
  423. response = self.do_request(query, features=features)
  424. assert response.status_code == 200, response.content
  425. assert len(response.data["data"]) == 1
  426. assert response.data["data"][0]["issue"] == "unknown"
  427. query = {"field": ["project", "issue"], "query": "!issue:unknown", "statsPeriod": "14d"}
  428. response = self.do_request(query, features=features)
  429. assert response.status_code == 200, response.content
  430. assert len(response.data["data"]) == 1
  431. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  432. def test_negative_user_search(self):
  433. project = self.create_project()
  434. user_data = {"email": "foo@example.com", "id": "123", "username": "foo"}
  435. # Load an event with data that shouldn't match
  436. data = load_data("transaction", timestamp=before_now(minutes=1))
  437. data["transaction"] = "/transactions/nomatch"
  438. event_user = user_data.copy()
  439. event_user["id"] = "undefined"
  440. data["user"] = event_user
  441. self.store_event(data, project_id=project.id)
  442. # Load a matching event
  443. data = load_data("transaction", timestamp=before_now(minutes=1))
  444. data["transaction"] = "/transactions/matching"
  445. data["user"] = user_data
  446. self.store_event(data, project_id=project.id)
  447. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  448. query = {
  449. "field": ["project", "user"],
  450. "query": '!user:"id:undefined"',
  451. "statsPeriod": "14d",
  452. }
  453. response = self.do_request(query, features=features)
  454. assert response.status_code == 200, response.content
  455. assert len(response.data["data"]) == 1
  456. assert response.data["data"][0]["user"] == "id:{}".format(user_data["id"])
  457. assert "user.email" not in response.data["data"][0]
  458. assert "user.id" not in response.data["data"][0]
  459. def test_not_project_in_query(self):
  460. project1 = self.create_project()
  461. project2 = self.create_project()
  462. self.store_event(
  463. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  464. project_id=project1.id,
  465. )
  466. self.store_event(
  467. data={"event_id": "b" * 32, "environment": "staging", "timestamp": self.min_ago},
  468. project_id=project2.id,
  469. )
  470. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  471. query = {
  472. "field": ["project", "count()"],
  473. "query": '!project:"%s"' % project1.slug,
  474. "statsPeriod": "14d",
  475. }
  476. response = self.do_request(query, features=features)
  477. assert response.status_code == 200, response.content
  478. assert len(response.data["data"]) == 1
  479. assert response.data["data"][0]["project"] == project2.slug
  480. assert "project.id" not in response.data["data"][0]
  481. def test_error_handled_condition(self):
  482. self.login_as(user=self.user)
  483. project = self.create_project()
  484. prototype = load_data("android-ndk")
  485. events = (
  486. ("a" * 32, "not handled", False),
  487. ("b" * 32, "was handled", True),
  488. ("c" * 32, "undefined", None),
  489. )
  490. for event in events:
  491. prototype["event_id"] = event[0]
  492. prototype["message"] = event[1]
  493. prototype["exception"]["values"][0]["value"] = event[1]
  494. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  495. prototype["timestamp"] = self.two_min_ago
  496. self.store_event(data=prototype, project_id=project.id)
  497. with self.feature("organizations:discover-basic"):
  498. query = {
  499. "field": ["message", "error.handled"],
  500. "query": "error.handled:0",
  501. "orderby": "message",
  502. }
  503. response = self.do_request(query)
  504. assert response.status_code == 200, response.data
  505. assert 1 == len(response.data["data"])
  506. assert [0] == response.data["data"][0]["error.handled"]
  507. with self.feature("organizations:discover-basic"):
  508. query = {
  509. "field": ["message", "error.handled"],
  510. "query": "error.handled:1",
  511. "orderby": "message",
  512. }
  513. response = self.do_request(query)
  514. assert response.status_code == 200, response.data
  515. assert 2 == len(response.data["data"])
  516. assert [None] == response.data["data"][0]["error.handled"]
  517. assert [1] == response.data["data"][1]["error.handled"]
  518. def test_error_unhandled_condition(self):
  519. self.login_as(user=self.user)
  520. project = self.create_project()
  521. prototype = load_data("android-ndk")
  522. events = (
  523. ("a" * 32, "not handled", False),
  524. ("b" * 32, "was handled", True),
  525. ("c" * 32, "undefined", None),
  526. )
  527. for event in events:
  528. prototype["event_id"] = event[0]
  529. prototype["message"] = event[1]
  530. prototype["exception"]["values"][0]["value"] = event[1]
  531. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  532. prototype["timestamp"] = self.two_min_ago
  533. self.store_event(data=prototype, project_id=project.id)
  534. with self.feature("organizations:discover-basic"):
  535. query = {
  536. "field": ["message", "error.unhandled", "error.handled"],
  537. "query": "error.unhandled:true",
  538. "orderby": "message",
  539. }
  540. response = self.do_request(query)
  541. assert response.status_code == 200, response.data
  542. assert 1 == len(response.data["data"])
  543. assert [0] == response.data["data"][0]["error.handled"]
  544. assert 1 == response.data["data"][0]["error.unhandled"]
  545. with self.feature("organizations:discover-basic"):
  546. query = {
  547. "field": ["message", "error.handled", "error.unhandled"],
  548. "query": "error.unhandled:false",
  549. "orderby": "message",
  550. }
  551. response = self.do_request(query)
  552. assert response.status_code == 200, response.data
  553. assert 2 == len(response.data["data"])
  554. assert [None] == response.data["data"][0]["error.handled"]
  555. assert 0 == response.data["data"][0]["error.unhandled"]
  556. assert [1] == response.data["data"][1]["error.handled"]
  557. assert 0 == response.data["data"][1]["error.unhandled"]
  558. def test_implicit_groupby(self):
  559. project = self.create_project()
  560. self.store_event(
  561. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  562. project_id=project.id,
  563. )
  564. event1 = self.store_event(
  565. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_1"]},
  566. project_id=project.id,
  567. )
  568. event2 = self.store_event(
  569. data={"event_id": "c" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  570. project_id=project.id,
  571. )
  572. query = {"field": ["count(id)", "project.id", "issue.id"], "orderby": "issue.id"}
  573. response = self.do_request(query)
  574. assert response.status_code == 200, response.content
  575. assert len(response.data["data"]) == 2
  576. data = response.data["data"]
  577. assert data[0] == {"project.id": project.id, "issue.id": event1.group_id, "count_id": 2}
  578. assert data[1] == {"project.id": project.id, "issue.id": event2.group_id, "count_id": 1}
  579. meta = response.data["meta"]
  580. assert meta["count_id"] == "integer"
  581. def test_orderby(self):
  582. project = self.create_project()
  583. self.store_event(
  584. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  585. )
  586. self.store_event(
  587. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project.id
  588. )
  589. self.store_event(
  590. data={"event_id": "c" * 32, "timestamp": self.min_ago}, project_id=project.id
  591. )
  592. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  593. response = self.do_request(query)
  594. assert response.status_code == 200, response.content
  595. data = response.data["data"]
  596. assert data[0]["id"] == "c" * 32
  597. assert data[1]["id"] == "b" * 32
  598. assert data[2]["id"] == "a" * 32
  599. def test_sort_title(self):
  600. project = self.create_project()
  601. self.store_event(
  602. data={"event_id": "a" * 32, "message": "zlast", "timestamp": self.two_min_ago},
  603. project_id=project.id,
  604. )
  605. self.store_event(
  606. data={"event_id": "b" * 32, "message": "second", "timestamp": self.min_ago},
  607. project_id=project.id,
  608. )
  609. self.store_event(
  610. data={"event_id": "c" * 32, "message": "first", "timestamp": self.min_ago},
  611. project_id=project.id,
  612. )
  613. query = {"field": ["id", "title"], "sort": "title"}
  614. response = self.do_request(query)
  615. assert response.status_code == 200, response.content
  616. data = response.data["data"]
  617. assert data[0]["id"] == "c" * 32
  618. assert data[1]["id"] == "b" * 32
  619. assert data[2]["id"] == "a" * 32
  620. def test_sort_invalid(self):
  621. project = self.create_project()
  622. self.store_event(
  623. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  624. )
  625. query = {"field": ["id"], "sort": "garbage"}
  626. response = self.do_request(query)
  627. assert response.status_code == 400
  628. assert "order by" in response.data["detail"]
  629. def test_latest_release_alias(self):
  630. project = self.create_project()
  631. event1 = self.store_event(
  632. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "release": "0.8"},
  633. project_id=project.id,
  634. )
  635. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  636. response = self.do_request(query)
  637. assert response.status_code == 200, response.content
  638. data = response.data["data"]
  639. assert data[0]["issue.id"] == event1.group_id
  640. assert data[0]["release"] == "0.8"
  641. event2 = self.store_event(
  642. data={"event_id": "a" * 32, "timestamp": self.min_ago, "release": "0.9"},
  643. project_id=project.id,
  644. )
  645. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  646. response = self.do_request(query)
  647. assert response.status_code == 200, response.content
  648. data = response.data["data"]
  649. assert data[0]["issue.id"] == event2.group_id
  650. assert data[0]["release"] == "0.9"
  651. def test_aliased_fields(self):
  652. project = self.create_project()
  653. event1 = self.store_event(
  654. data={
  655. "event_id": "a" * 32,
  656. "timestamp": self.min_ago,
  657. "fingerprint": ["group_1"],
  658. "user": {"email": "foo@example.com"},
  659. },
  660. project_id=project.id,
  661. )
  662. event2 = self.store_event(
  663. data={
  664. "event_id": "b" * 32,
  665. "timestamp": self.min_ago,
  666. "fingerprint": ["group_2"],
  667. "user": {"email": "foo@example.com"},
  668. },
  669. project_id=project.id,
  670. )
  671. self.store_event(
  672. data={
  673. "event_id": "c" * 32,
  674. "timestamp": self.min_ago,
  675. "fingerprint": ["group_2"],
  676. "user": {"email": "bar@example.com"},
  677. },
  678. project_id=project.id,
  679. )
  680. query = {"field": ["issue.id", "count(id)", "count_unique(user)"], "orderby": "issue.id"}
  681. response = self.do_request(query)
  682. assert response.status_code == 200, response.content
  683. assert len(response.data["data"]) == 2
  684. data = response.data["data"]
  685. assert data[0]["issue.id"] == event1.group_id
  686. assert data[0]["count_id"] == 1
  687. assert data[0]["count_unique_user"] == 1
  688. assert "projectid" not in data[0]
  689. assert "project.id" not in data[0]
  690. assert data[1]["issue.id"] == event2.group_id
  691. assert data[1]["count_id"] == 2
  692. assert data[1]["count_unique_user"] == 2
  693. def test_aggregate_field_with_dotted_param(self):
  694. project = self.create_project()
  695. event1 = self.store_event(
  696. data={
  697. "event_id": "a" * 32,
  698. "timestamp": self.min_ago,
  699. "fingerprint": ["group_1"],
  700. "user": {"id": "123", "email": "foo@example.com"},
  701. },
  702. project_id=project.id,
  703. )
  704. event2 = self.store_event(
  705. data={
  706. "event_id": "b" * 32,
  707. "timestamp": self.min_ago,
  708. "fingerprint": ["group_2"],
  709. "user": {"id": "123", "email": "foo@example.com"},
  710. },
  711. project_id=project.id,
  712. )
  713. self.store_event(
  714. data={
  715. "event_id": "c" * 32,
  716. "timestamp": self.min_ago,
  717. "fingerprint": ["group_2"],
  718. "user": {"id": "456", "email": "bar@example.com"},
  719. },
  720. project_id=project.id,
  721. )
  722. query = {
  723. "field": ["issue.id", "issue_title", "count(id)", "count_unique(user.email)"],
  724. "orderby": "issue.id",
  725. }
  726. response = self.do_request(query)
  727. assert response.status_code == 200, response.content
  728. assert len(response.data["data"]) == 2
  729. data = response.data["data"]
  730. assert data[0]["issue.id"] == event1.group_id
  731. assert data[0]["count_id"] == 1
  732. assert data[0]["count_unique_user_email"] == 1
  733. assert "projectid" not in data[0]
  734. assert "project.id" not in data[0]
  735. assert data[1]["issue.id"] == event2.group_id
  736. assert data[1]["count_id"] == 2
  737. assert data[1]["count_unique_user_email"] == 2
  738. def test_failure_rate_alias_field(self):
  739. project = self.create_project()
  740. data = load_data("transaction", timestamp=before_now(minutes=1))
  741. data["transaction"] = "/failure_rate/success"
  742. self.store_event(data, project_id=project.id)
  743. data = load_data("transaction", timestamp=before_now(minutes=1))
  744. data["transaction"] = "/failure_rate/unknown"
  745. data["contexts"]["trace"]["status"] = "unknown_error"
  746. self.store_event(data, project_id=project.id)
  747. for i in range(6):
  748. data = load_data("transaction", timestamp=before_now(minutes=1))
  749. data["transaction"] = f"/failure_rate/{i}"
  750. data["contexts"]["trace"]["status"] = "unauthenticated"
  751. self.store_event(data, project_id=project.id)
  752. query = {"field": ["failure_rate()"], "query": "event.type:transaction"}
  753. response = self.do_request(query)
  754. assert response.status_code == 200, response.content
  755. assert len(response.data["data"]) == 1
  756. data = response.data["data"]
  757. assert data[0]["failure_rate"] == 0.75
  758. def test_count_miserable_alias_field(self):
  759. project = self.create_project()
  760. events = [
  761. ("one", 300),
  762. ("one", 300),
  763. ("two", 3000),
  764. ("two", 3000),
  765. ("three", 300),
  766. ("three", 3000),
  767. ]
  768. for idx, event in enumerate(events):
  769. data = load_data(
  770. "transaction",
  771. timestamp=before_now(minutes=(1 + idx)),
  772. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  773. )
  774. data["event_id"] = f"{idx}" * 32
  775. data["transaction"] = f"/count_miserable/horribilis/{idx}"
  776. data["user"] = {"email": f"{event[0]}@example.com"}
  777. self.store_event(data, project_id=project.id)
  778. query = {"field": ["count_miserable(user, 300)"], "query": "event.type:transaction"}
  779. response = self.do_request(query)
  780. assert response.status_code == 200, response.content
  781. assert len(response.data["data"]) == 1
  782. data = response.data["data"]
  783. assert data[0]["count_miserable_user_300"] == 2
  784. def test_user_misery_alias_field(self):
  785. project = self.create_project()
  786. events = [
  787. ("one", 300),
  788. ("one", 300),
  789. ("two", 3000),
  790. ("two", 3000),
  791. ("three", 300),
  792. ("three", 3000),
  793. ]
  794. for idx, event in enumerate(events):
  795. data = load_data(
  796. "transaction",
  797. timestamp=before_now(minutes=(1 + idx)),
  798. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  799. )
  800. data["event_id"] = f"{idx}" * 32
  801. data["transaction"] = f"/user_misery/{idx}"
  802. data["user"] = {"email": f"{event[0]}@example.com"}
  803. self.store_event(data, project_id=project.id)
  804. query = {"field": ["user_misery(300)"], "query": "event.type:transaction"}
  805. response = self.do_request(query)
  806. assert response.status_code == 200, response.content
  807. assert len(response.data["data"]) == 1
  808. data = response.data["data"]
  809. assert abs(data[0]["user_misery_300"] - 0.0653) < 0.0001
  810. def test_aggregation(self):
  811. project = self.create_project()
  812. self.store_event(
  813. data={
  814. "event_id": "a" * 32,
  815. "timestamp": self.min_ago,
  816. "fingerprint": ["group_1"],
  817. "user": {"email": "foo@example.com"},
  818. "environment": "prod",
  819. "tags": {"sub_customer.is-Enterprise-42": "1"},
  820. },
  821. project_id=project.id,
  822. )
  823. self.store_event(
  824. data={
  825. "event_id": "b" * 32,
  826. "timestamp": self.min_ago,
  827. "fingerprint": ["group_2"],
  828. "user": {"email": "foo@example.com"},
  829. "environment": "staging",
  830. "tags": {"sub_customer.is-Enterprise-42": "1"},
  831. },
  832. project_id=project.id,
  833. )
  834. self.store_event(
  835. data={
  836. "event_id": "c" * 32,
  837. "timestamp": self.min_ago,
  838. "fingerprint": ["group_2"],
  839. "user": {"email": "foo@example.com"},
  840. "environment": "prod",
  841. "tags": {"sub_customer.is-Enterprise-42": "0"},
  842. },
  843. project_id=project.id,
  844. )
  845. self.store_event(
  846. data={
  847. "event_id": "d" * 32,
  848. "timestamp": self.min_ago,
  849. "fingerprint": ["group_2"],
  850. "user": {"email": "foo@example.com"},
  851. "environment": "prod",
  852. "tags": {"sub_customer.is-Enterprise-42": "1"},
  853. },
  854. project_id=project.id,
  855. )
  856. query = {
  857. "field": ["sub_customer.is-Enterprise-42", "count(sub_customer.is-Enterprise-42)"],
  858. "orderby": "sub_customer.is-Enterprise-42",
  859. }
  860. response = self.do_request(query)
  861. assert response.status_code == 200, response.content
  862. assert len(response.data["data"]) == 2
  863. data = response.data["data"]
  864. assert data[0]["count_sub_customer_is_Enterprise_42"] == 1
  865. assert data[1]["count_sub_customer_is_Enterprise_42"] == 3
  866. def test_aggregation_comparison(self):
  867. project = self.create_project()
  868. self.store_event(
  869. data={
  870. "event_id": "a" * 32,
  871. "timestamp": self.min_ago,
  872. "fingerprint": ["group_1"],
  873. "user": {"email": "foo@example.com"},
  874. },
  875. project_id=project.id,
  876. )
  877. event = self.store_event(
  878. data={
  879. "event_id": "b" * 32,
  880. "timestamp": self.min_ago,
  881. "fingerprint": ["group_2"],
  882. "user": {"email": "foo@example.com"},
  883. },
  884. project_id=project.id,
  885. )
  886. self.store_event(
  887. data={
  888. "event_id": "c" * 32,
  889. "timestamp": self.min_ago,
  890. "fingerprint": ["group_2"],
  891. "user": {"email": "bar@example.com"},
  892. },
  893. project_id=project.id,
  894. )
  895. self.store_event(
  896. data={
  897. "event_id": "d" * 32,
  898. "timestamp": self.min_ago,
  899. "fingerprint": ["group_3"],
  900. "user": {"email": "bar@example.com"},
  901. },
  902. project_id=project.id,
  903. )
  904. self.store_event(
  905. data={
  906. "event_id": "e" * 32,
  907. "timestamp": self.min_ago,
  908. "fingerprint": ["group_3"],
  909. "user": {"email": "bar@example.com"},
  910. },
  911. project_id=project.id,
  912. )
  913. query = {
  914. "field": ["issue.id", "count(id)", "count_unique(user)"],
  915. "query": "count(id):>1 count_unique(user):>1",
  916. "orderby": "issue.id",
  917. }
  918. response = self.do_request(query)
  919. assert response.status_code == 200, response.content
  920. assert len(response.data["data"]) == 1
  921. data = response.data["data"]
  922. assert data[0]["issue.id"] == event.group_id
  923. assert data[0]["count_id"] == 2
  924. assert data[0]["count_unique_user"] == 2
  925. def test_aggregation_alias_comparison(self):
  926. project = self.create_project()
  927. data = load_data(
  928. "transaction",
  929. timestamp=before_now(minutes=1),
  930. start_timestamp=before_now(minutes=1, seconds=5),
  931. )
  932. data["transaction"] = "/aggregates/1"
  933. self.store_event(data, project_id=project.id)
  934. data = load_data(
  935. "transaction",
  936. timestamp=before_now(minutes=1),
  937. start_timestamp=before_now(minutes=1, seconds=3),
  938. )
  939. data["transaction"] = "/aggregates/2"
  940. event = self.store_event(data, project_id=project.id)
  941. query = {
  942. "field": ["transaction", "p95()"],
  943. "query": "event.type:transaction p95():<4000",
  944. "orderby": ["transaction"],
  945. }
  946. response = self.do_request(query)
  947. assert response.status_code == 200, response.content
  948. assert len(response.data["data"]) == 1
  949. data = response.data["data"]
  950. assert data[0]["transaction"] == event.transaction
  951. assert data[0]["p95"] == 3000
  952. def test_aggregation_comparison_with_conditions(self):
  953. project = self.create_project()
  954. self.store_event(
  955. data={
  956. "event_id": "a" * 32,
  957. "timestamp": self.min_ago,
  958. "fingerprint": ["group_1"],
  959. "user": {"email": "foo@example.com"},
  960. "environment": "prod",
  961. },
  962. project_id=project.id,
  963. )
  964. self.store_event(
  965. data={
  966. "event_id": "b" * 32,
  967. "timestamp": self.min_ago,
  968. "fingerprint": ["group_2"],
  969. "user": {"email": "foo@example.com"},
  970. "environment": "staging",
  971. },
  972. project_id=project.id,
  973. )
  974. event = self.store_event(
  975. data={
  976. "event_id": "c" * 32,
  977. "timestamp": self.min_ago,
  978. "fingerprint": ["group_2"],
  979. "user": {"email": "foo@example.com"},
  980. "environment": "prod",
  981. },
  982. project_id=project.id,
  983. )
  984. self.store_event(
  985. data={
  986. "event_id": "d" * 32,
  987. "timestamp": self.min_ago,
  988. "fingerprint": ["group_2"],
  989. "user": {"email": "foo@example.com"},
  990. "environment": "prod",
  991. },
  992. project_id=project.id,
  993. )
  994. query = {
  995. "field": ["issue.id", "count(id)"],
  996. "query": "count(id):>1 user.email:foo@example.com environment:prod",
  997. "orderby": "issue.id",
  998. }
  999. response = self.do_request(query)
  1000. assert response.status_code == 200, response.content
  1001. assert len(response.data["data"]) == 1
  1002. data = response.data["data"]
  1003. assert data[0]["issue.id"] == event.group_id
  1004. assert data[0]["count_id"] == 2
  1005. def test_aggregation_date_comparison_with_conditions(self):
  1006. project = self.create_project()
  1007. event = self.store_event(
  1008. data={
  1009. "event_id": "a" * 32,
  1010. "timestamp": self.min_ago,
  1011. "fingerprint": ["group_1"],
  1012. "user": {"email": "foo@example.com"},
  1013. "environment": "prod",
  1014. },
  1015. project_id=project.id,
  1016. )
  1017. self.store_event(
  1018. data={
  1019. "event_id": "b" * 32,
  1020. "timestamp": self.min_ago,
  1021. "fingerprint": ["group_2"],
  1022. "user": {"email": "foo@example.com"},
  1023. "environment": "staging",
  1024. },
  1025. project_id=project.id,
  1026. )
  1027. self.store_event(
  1028. data={
  1029. "event_id": "c" * 32,
  1030. "timestamp": self.min_ago,
  1031. "fingerprint": ["group_2"],
  1032. "user": {"email": "foo@example.com"},
  1033. "environment": "prod",
  1034. },
  1035. project_id=project.id,
  1036. )
  1037. self.store_event(
  1038. data={
  1039. "event_id": "d" * 32,
  1040. "timestamp": self.min_ago,
  1041. "fingerprint": ["group_2"],
  1042. "user": {"email": "foo@example.com"},
  1043. "environment": "prod",
  1044. },
  1045. project_id=project.id,
  1046. )
  1047. query = {
  1048. "field": ["issue.id", "max(timestamp)"],
  1049. "query": "max(timestamp):>1 user.email:foo@example.com environment:prod",
  1050. "orderby": "issue.id",
  1051. }
  1052. response = self.do_request(query)
  1053. assert response.status_code == 200, response.content
  1054. assert len(response.data["data"]) == 2
  1055. response.data["meta"]["max_timestamp"] == "date"
  1056. data = response.data["data"]
  1057. assert data[0]["issue.id"] == event.group_id
  1058. def test_percentile_function(self):
  1059. project = self.create_project()
  1060. data = load_data(
  1061. "transaction",
  1062. timestamp=before_now(minutes=1),
  1063. start_timestamp=before_now(minutes=1, seconds=5),
  1064. )
  1065. data["transaction"] = "/aggregates/1"
  1066. event1 = self.store_event(data, project_id=project.id)
  1067. data = load_data(
  1068. "transaction",
  1069. timestamp=before_now(minutes=1),
  1070. start_timestamp=before_now(minutes=1, seconds=3),
  1071. )
  1072. data["transaction"] = "/aggregates/2"
  1073. event2 = self.store_event(data, project_id=project.id)
  1074. query = {
  1075. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  1076. "query": "event.type:transaction",
  1077. "orderby": ["transaction"],
  1078. }
  1079. response = self.do_request(query)
  1080. assert response.status_code == 200, response.content
  1081. assert len(response.data["data"]) == 2
  1082. data = response.data["data"]
  1083. assert data[0]["transaction"] == event1.transaction
  1084. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  1085. assert data[1]["transaction"] == event2.transaction
  1086. assert data[1]["percentile_transaction_duration_0_95"] == 3000
  1087. def test_percentile_function_as_condition(self):
  1088. project = self.create_project()
  1089. data = load_data(
  1090. "transaction",
  1091. timestamp=before_now(minutes=1),
  1092. start_timestamp=before_now(minutes=1, seconds=5),
  1093. )
  1094. data["transaction"] = "/aggregates/1"
  1095. event1 = self.store_event(data, project_id=project.id)
  1096. data = load_data(
  1097. "transaction",
  1098. timestamp=before_now(minutes=1),
  1099. start_timestamp=before_now(minutes=1, seconds=3),
  1100. )
  1101. data["transaction"] = "/aggregates/2"
  1102. self.store_event(data, project_id=project.id)
  1103. query = {
  1104. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  1105. "query": "event.type:transaction percentile(transaction.duration, 0.95):>4000",
  1106. "orderby": ["transaction"],
  1107. }
  1108. response = self.do_request(query)
  1109. assert response.status_code == 200, response.content
  1110. assert len(response.data["data"]) == 1
  1111. data = response.data["data"]
  1112. assert data[0]["transaction"] == event1.transaction
  1113. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  1114. def test_epm_function(self):
  1115. project = self.create_project()
  1116. data = load_data(
  1117. "transaction",
  1118. timestamp=before_now(minutes=1),
  1119. start_timestamp=before_now(minutes=1, seconds=5),
  1120. )
  1121. data["transaction"] = "/aggregates/1"
  1122. event1 = self.store_event(data, project_id=project.id)
  1123. data = load_data(
  1124. "transaction",
  1125. timestamp=before_now(minutes=1),
  1126. start_timestamp=before_now(minutes=1, seconds=3),
  1127. )
  1128. data["transaction"] = "/aggregates/2"
  1129. event2 = self.store_event(data, project_id=project.id)
  1130. query = {
  1131. "field": ["transaction", "epm()"],
  1132. "query": "event.type:transaction",
  1133. "orderby": ["transaction"],
  1134. "statsPeriod": "2m",
  1135. }
  1136. response = self.do_request(query)
  1137. assert response.status_code == 200, response.content
  1138. assert len(response.data["data"]) == 2
  1139. data = response.data["data"]
  1140. assert data[0]["transaction"] == event1.transaction
  1141. assert data[0]["epm"] == 0.5
  1142. assert data[1]["transaction"] == event2.transaction
  1143. assert data[1]["epm"] == 0.5
  1144. def test_nonexistent_fields(self):
  1145. project = self.create_project()
  1146. self.store_event(
  1147. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1148. project_id=project.id,
  1149. )
  1150. query = {"field": ["issue_world.id"]}
  1151. response = self.do_request(query)
  1152. assert response.status_code == 200, response.content
  1153. assert response.data["data"][0]["issue_world.id"] == ""
  1154. def test_no_requested_fields_or_grouping(self):
  1155. project = self.create_project()
  1156. self.store_event(
  1157. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1158. project_id=project.id,
  1159. )
  1160. query = {"query": "test"}
  1161. response = self.do_request(query)
  1162. assert response.status_code == 400, response.content
  1163. assert response.data["detail"] == "No columns selected"
  1164. def test_condition_on_aggregate_misses(self):
  1165. project = self.create_project()
  1166. self.store_event(
  1167. data={
  1168. "event_id": "c" * 32,
  1169. "timestamp": self.min_ago,
  1170. "fingerprint": ["group_2"],
  1171. "user": {"email": "bar@example.com"},
  1172. },
  1173. project_id=project.id,
  1174. )
  1175. query = {"field": ["issue.id"], "query": "event_count:>0", "orderby": "issue.id"}
  1176. response = self.do_request(query)
  1177. assert response.status_code == 200, response.content
  1178. assert len(response.data["data"]) == 0
  1179. def test_next_prev_link_headers(self):
  1180. project = self.create_project()
  1181. events = [("a", "group_1"), ("b", "group_2"), ("c", "group_2"), ("d", "group_2")]
  1182. for e in events:
  1183. self.store_event(
  1184. data={
  1185. "event_id": e[0] * 32,
  1186. "timestamp": self.min_ago,
  1187. "fingerprint": [e[1]],
  1188. "user": {"email": "foo@example.com"},
  1189. "tags": {"language": "C++"},
  1190. },
  1191. project_id=project.id,
  1192. )
  1193. query = {
  1194. "field": ["count(id)", "issue.id", "context.key"],
  1195. "sort": "-count_id",
  1196. "query": "language:C++",
  1197. }
  1198. response = self.do_request(query)
  1199. assert response.status_code == 200, response.content
  1200. links = parse_link_header(response["Link"])
  1201. for link in links:
  1202. assert "field=issue.id" in link
  1203. assert "field=count%28id%29" in link
  1204. assert "field=context.key" in link
  1205. assert "sort=-count_id" in link
  1206. assert "query=language%3AC%2B%2B" in link
  1207. assert len(response.data["data"]) == 2
  1208. data = response.data["data"]
  1209. assert data[0]["count_id"] == 3
  1210. assert data[1]["count_id"] == 1
  1211. def test_empty_count_query(self):
  1212. project = self.create_project()
  1213. event = self.store_event(
  1214. data={
  1215. "event_id": "a" * 32,
  1216. "timestamp": iso_format(before_now(minutes=5)),
  1217. "fingerprint": ["1123581321"],
  1218. "user": {"email": "foo@example.com"},
  1219. "tags": {"language": "C++"},
  1220. },
  1221. project_id=project.id,
  1222. )
  1223. query = {
  1224. "field": ["count()"],
  1225. "query": "issue.id:%d timestamp:>%s" % (event.group_id, self.min_ago),
  1226. "statsPeriod": "14d",
  1227. }
  1228. response = self.do_request(query)
  1229. assert response.status_code == 200, response.content
  1230. data = response.data["data"]
  1231. assert len(data) == 1
  1232. assert data[0]["count"] == 0
  1233. def test_stack_wildcard_condition(self):
  1234. project = self.create_project()
  1235. data = load_data("javascript")
  1236. data["timestamp"] = self.min_ago
  1237. self.store_event(data=data, project_id=project.id)
  1238. query = {"field": ["stack.filename", "message"], "query": "stack.filename:*.js"}
  1239. response = self.do_request(query)
  1240. assert response.status_code == 200, response.content
  1241. assert len(response.data["data"]) == 1
  1242. assert response.data["meta"]["message"] == "string"
  1243. def test_email_wildcard_condition(self):
  1244. project = self.create_project()
  1245. data = load_data("javascript")
  1246. data["timestamp"] = self.min_ago
  1247. self.store_event(data=data, project_id=project.id)
  1248. query = {"field": ["stack.filename", "message"], "query": "user.email:*@example.org"}
  1249. response = self.do_request(query)
  1250. assert response.status_code == 200, response.content
  1251. assert len(response.data["data"]) == 1
  1252. assert response.data["meta"]["message"] == "string"
  1253. def test_transaction_event_type(self):
  1254. project = self.create_project()
  1255. data = load_data(
  1256. "transaction",
  1257. timestamp=before_now(minutes=1),
  1258. start_timestamp=before_now(minutes=1, seconds=5),
  1259. )
  1260. self.store_event(data=data, project_id=project.id)
  1261. query = {
  1262. "field": ["transaction", "transaction.duration", "transaction.status"],
  1263. "query": "event.type:transaction",
  1264. }
  1265. response = self.do_request(query)
  1266. assert response.status_code == 200, response.content
  1267. assert len(response.data["data"]) == 1
  1268. assert response.data["meta"]["transaction.duration"] == "duration"
  1269. assert response.data["meta"]["transaction.status"] == "string"
  1270. assert response.data["data"][0]["transaction.status"] == "ok"
  1271. def test_trace_columns(self):
  1272. project = self.create_project()
  1273. data = load_data(
  1274. "transaction",
  1275. timestamp=before_now(minutes=1),
  1276. start_timestamp=before_now(minutes=1, seconds=5),
  1277. )
  1278. self.store_event(data=data, project_id=project.id)
  1279. query = {"field": ["trace"], "query": "event.type:transaction"}
  1280. response = self.do_request(query)
  1281. assert response.status_code == 200, response.content
  1282. assert len(response.data["data"]) == 1
  1283. assert response.data["meta"]["trace"] == "string"
  1284. assert response.data["data"][0]["trace"] == data["contexts"]["trace"]["trace_id"]
  1285. def test_issue_in_columns(self):
  1286. project1 = self.create_project()
  1287. project2 = self.create_project()
  1288. event1 = self.store_event(
  1289. data={
  1290. "event_id": "a" * 32,
  1291. "transaction": "/example",
  1292. "message": "how to make fast",
  1293. "timestamp": self.two_min_ago,
  1294. "fingerprint": ["group_1"],
  1295. },
  1296. project_id=project1.id,
  1297. )
  1298. event2 = self.store_event(
  1299. data={
  1300. "event_id": "b" * 32,
  1301. "transaction": "/example",
  1302. "message": "how to make fast",
  1303. "timestamp": self.two_min_ago,
  1304. "fingerprint": ["group_1"],
  1305. },
  1306. project_id=project2.id,
  1307. )
  1308. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1309. query = {"field": ["id", "issue"], "orderby": ["id"]}
  1310. response = self.do_request(query, features=features)
  1311. assert response.status_code == 200, response.content
  1312. data = response.data["data"]
  1313. assert len(data) == 2
  1314. assert data[0]["id"] == event1.event_id
  1315. assert data[0]["issue.id"] == event1.group_id
  1316. assert data[0]["issue"] == event1.group.qualified_short_id
  1317. assert data[1]["id"] == event2.event_id
  1318. assert data[1]["issue.id"] == event2.group_id
  1319. assert data[1]["issue"] == event2.group.qualified_short_id
  1320. def test_issue_in_search_and_columns(self):
  1321. project1 = self.create_project()
  1322. project2 = self.create_project()
  1323. event1 = self.store_event(
  1324. data={
  1325. "event_id": "a" * 32,
  1326. "transaction": "/example",
  1327. "message": "how to make fast",
  1328. "timestamp": self.two_min_ago,
  1329. "fingerprint": ["group_1"],
  1330. },
  1331. project_id=project1.id,
  1332. )
  1333. self.store_event(
  1334. data={
  1335. "event_id": "b" * 32,
  1336. "transaction": "/example",
  1337. "message": "how to make fast",
  1338. "timestamp": self.two_min_ago,
  1339. "fingerprint": ["group_1"],
  1340. },
  1341. project_id=project2.id,
  1342. )
  1343. tests = [
  1344. ("issue", "issue:%s" % event1.group.qualified_short_id),
  1345. ("issue.id", "issue:%s" % event1.group.qualified_short_id),
  1346. ("issue", "issue.id:%s" % event1.group_id),
  1347. ("issue.id", "issue.id:%s" % event1.group_id),
  1348. ]
  1349. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1350. for testdata in tests:
  1351. query = {"field": [testdata[0]], "query": testdata[1]}
  1352. response = self.do_request(query, features=features)
  1353. assert response.status_code == 200, response.content
  1354. data = response.data["data"]
  1355. assert len(data) == 1
  1356. assert data[0]["id"] == event1.event_id
  1357. assert data[0]["issue.id"] == event1.group_id
  1358. if testdata[0] == "issue":
  1359. assert data[0]["issue"] == event1.group.qualified_short_id
  1360. else:
  1361. assert data[0].get("issue", None) is None
  1362. def test_issue_negation(self):
  1363. project1 = self.create_project()
  1364. project2 = self.create_project()
  1365. event1 = self.store_event(
  1366. data={
  1367. "event_id": "a" * 32,
  1368. "transaction": "/example",
  1369. "message": "how to make fast",
  1370. "timestamp": self.two_min_ago,
  1371. "fingerprint": ["group_1"],
  1372. },
  1373. project_id=project1.id,
  1374. )
  1375. event2 = self.store_event(
  1376. data={
  1377. "event_id": "b" * 32,
  1378. "transaction": "/example",
  1379. "message": "go really fast plz",
  1380. "timestamp": self.two_min_ago,
  1381. "fingerprint": ["group_2"],
  1382. },
  1383. project_id=project2.id,
  1384. )
  1385. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1386. query = {
  1387. "field": ["title", "issue.id"],
  1388. "query": f"!issue:{event1.group.qualified_short_id}",
  1389. }
  1390. response = self.do_request(query, features=features)
  1391. assert response.status_code == 200, response.content
  1392. data = response.data["data"]
  1393. assert len(data) == 1
  1394. assert data[0]["title"] == event2.title
  1395. assert data[0]["issue.id"] == event2.group_id
  1396. def test_search_for_nonexistent_issue(self):
  1397. project1 = self.create_project()
  1398. self.store_event(
  1399. data={
  1400. "event_id": "a" * 32,
  1401. "transaction": "/example",
  1402. "message": "how to make fast",
  1403. "timestamp": self.two_min_ago,
  1404. "fingerprint": ["group_1"],
  1405. },
  1406. project_id=project1.id,
  1407. )
  1408. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1409. query = {"field": ["count()"], "query": "issue.id:112358"}
  1410. response = self.do_request(query, features=features)
  1411. assert response.status_code == 200, response.content
  1412. data = response.data["data"]
  1413. assert len(data) == 1
  1414. assert data[0]["count"] == 0
  1415. def test_issue_alias_inside_aggregate(self):
  1416. project1 = self.create_project()
  1417. self.store_event(
  1418. data={
  1419. "event_id": "a" * 32,
  1420. "transaction": "/example",
  1421. "message": "how to make fast",
  1422. "timestamp": self.two_min_ago,
  1423. "fingerprint": ["group_1"],
  1424. },
  1425. project_id=project1.id,
  1426. )
  1427. self.store_event(
  1428. data={
  1429. "event_id": "b" * 32,
  1430. "transaction": "/example",
  1431. "message": "how to make fast",
  1432. "timestamp": self.two_min_ago,
  1433. "fingerprint": ["group_2"],
  1434. },
  1435. project_id=project1.id,
  1436. )
  1437. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1438. query = {
  1439. "field": ["project", "count(id)", "count_unique(issue.id)", "count_unique(issue)"],
  1440. "sort": "-count(id)",
  1441. "statsPeriod": "24h",
  1442. }
  1443. response = self.do_request(query, features=features)
  1444. assert response.status_code == 200, response.content
  1445. data = response.data["data"]
  1446. assert len(data) == 1
  1447. assert data[0]["count_id"] == 2
  1448. assert data[0]["count_unique_issue_id"] == 2
  1449. assert data[0]["count_unique_issue"] == 2
  1450. def test_project_alias_inside_aggregate(self):
  1451. project1 = self.create_project()
  1452. project2 = self.create_project()
  1453. self.store_event(
  1454. data={
  1455. "event_id": "a" * 32,
  1456. "transaction": "/example",
  1457. "message": "how to make fast",
  1458. "timestamp": self.two_min_ago,
  1459. "fingerprint": ["group_1"],
  1460. },
  1461. project_id=project1.id,
  1462. )
  1463. self.store_event(
  1464. data={
  1465. "event_id": "b" * 32,
  1466. "transaction": "/example",
  1467. "message": "how to make fast",
  1468. "timestamp": self.two_min_ago,
  1469. "fingerprint": ["group_2"],
  1470. },
  1471. project_id=project2.id,
  1472. )
  1473. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1474. query = {
  1475. "field": [
  1476. "event.type",
  1477. "count(id)",
  1478. "count_unique(project.id)",
  1479. "count_unique(project)",
  1480. ],
  1481. "sort": "-count(id)",
  1482. "statsPeriod": "24h",
  1483. }
  1484. response = self.do_request(query, features=features)
  1485. assert response.status_code == 200, response.content
  1486. data = response.data["data"]
  1487. assert len(data) == 1
  1488. assert data[0]["count_id"] == 2
  1489. assert data[0]["count_unique_project_id"] == 2
  1490. assert data[0]["count_unique_project"] == 2
  1491. def test_user_display(self):
  1492. project1 = self.create_project()
  1493. project2 = self.create_project()
  1494. self.store_event(
  1495. data={
  1496. "event_id": "a" * 32,
  1497. "transaction": "/example",
  1498. "message": "how to make fast",
  1499. "timestamp": self.two_min_ago,
  1500. "user": {"email": "cathy@example.com"},
  1501. },
  1502. project_id=project1.id,
  1503. )
  1504. self.store_event(
  1505. data={
  1506. "event_id": "b" * 32,
  1507. "transaction": "/example",
  1508. "message": "how to make fast",
  1509. "timestamp": self.two_min_ago,
  1510. "user": {"username": "catherine"},
  1511. },
  1512. project_id=project2.id,
  1513. )
  1514. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1515. query = {
  1516. "field": ["event.type", "user.display"],
  1517. "query": "user.display:cath*",
  1518. "statsPeriod": "24h",
  1519. }
  1520. response = self.do_request(query, features=features)
  1521. assert response.status_code == 200, response.content
  1522. data = response.data["data"]
  1523. assert len(data) == 2
  1524. result = {r["user.display"] for r in data}
  1525. assert result == {"catherine", "cathy@example.com"}
  1526. def test_user_display_with_aggregates(self):
  1527. self.login_as(user=self.user)
  1528. project1 = self.create_project()
  1529. self.store_event(
  1530. data={
  1531. "event_id": "a" * 32,
  1532. "transaction": "/example",
  1533. "message": "how to make fast",
  1534. "timestamp": self.two_min_ago,
  1535. "user": {"email": "cathy@example.com"},
  1536. },
  1537. project_id=project1.id,
  1538. )
  1539. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1540. query = {
  1541. "field": ["event.type", "user.display", "count_unique(title)"],
  1542. "statsPeriod": "24h",
  1543. }
  1544. response = self.do_request(query, features=features)
  1545. assert response.status_code == 200, response.content
  1546. data = response.data["data"]
  1547. assert len(data) == 1
  1548. result = {r["user.display"] for r in data}
  1549. assert result == {"cathy@example.com"}
  1550. query = {"field": ["event.type", "count_unique(user.display)"], "statsPeriod": "24h"}
  1551. response = self.do_request(query, features=features)
  1552. assert response.status_code == 200, response.content
  1553. data = response.data["data"]
  1554. assert len(data) == 1
  1555. assert data[0]["count_unique_user_display"] == 1
  1556. def test_orderby_user_display(self):
  1557. project1 = self.create_project()
  1558. project2 = self.create_project()
  1559. self.store_event(
  1560. data={
  1561. "event_id": "a" * 32,
  1562. "transaction": "/example",
  1563. "message": "how to make fast",
  1564. "timestamp": self.two_min_ago,
  1565. "user": {"email": "cathy@example.com"},
  1566. },
  1567. project_id=project1.id,
  1568. )
  1569. self.store_event(
  1570. data={
  1571. "event_id": "b" * 32,
  1572. "transaction": "/example",
  1573. "message": "how to make fast",
  1574. "timestamp": self.two_min_ago,
  1575. "user": {"username": "catherine"},
  1576. },
  1577. project_id=project2.id,
  1578. )
  1579. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1580. query = {
  1581. "field": ["event.type", "user.display"],
  1582. "query": "user.display:cath*",
  1583. "statsPeriod": "24h",
  1584. "orderby": "-user.display",
  1585. }
  1586. response = self.do_request(query, features=features)
  1587. assert response.status_code == 200, response.content
  1588. data = response.data["data"]
  1589. assert len(data) == 2
  1590. result = [r["user.display"] for r in data]
  1591. # because we're ordering by `-user.display`, we expect the results in reverse sorted order
  1592. assert result == ["cathy@example.com", "catherine"]
  1593. def test_orderby_user_display_with_aggregates(self):
  1594. project1 = self.create_project()
  1595. project2 = self.create_project()
  1596. self.store_event(
  1597. data={
  1598. "event_id": "a" * 32,
  1599. "transaction": "/example",
  1600. "message": "how to make fast",
  1601. "timestamp": self.two_min_ago,
  1602. "user": {"email": "cathy@example.com"},
  1603. },
  1604. project_id=project1.id,
  1605. )
  1606. self.store_event(
  1607. data={
  1608. "event_id": "b" * 32,
  1609. "transaction": "/example",
  1610. "message": "how to make fast",
  1611. "timestamp": self.two_min_ago,
  1612. "user": {"username": "catherine"},
  1613. },
  1614. project_id=project2.id,
  1615. )
  1616. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1617. query = {
  1618. "field": ["event.type", "user.display", "count_unique(title)"],
  1619. "query": "user.display:cath*",
  1620. "statsPeriod": "24h",
  1621. "orderby": "user.display",
  1622. }
  1623. response = self.do_request(query, features=features)
  1624. assert response.status_code == 200, response.content
  1625. data = response.data["data"]
  1626. assert len(data) == 2
  1627. result = [r["user.display"] for r in data]
  1628. # because we're ordering by `user.display`, we expect the results in sorted order
  1629. assert result == ["catherine", "cathy@example.com"]
  1630. @pytest.mark.skip(
  1631. """
  1632. For some reason ClickHouse errors when there are two of the same string literals
  1633. (in this case the empty string "") in a query and one is in the prewhere clause.
  1634. Does not affect production or ClickHouse versions > 20.4.
  1635. """
  1636. )
  1637. def test_has_message(self):
  1638. project = self.create_project()
  1639. event = self.store_event(
  1640. {"timestamp": iso_format(before_now(minutes=1)), "message": "a"}, project_id=project.id
  1641. )
  1642. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1643. query = {"field": ["project", "message"], "query": "has:message", "statsPeriod": "14d"}
  1644. response = self.do_request(query, features=features)
  1645. assert response.status_code == 200, response.content
  1646. assert len(response.data["data"]) == 1
  1647. assert response.data["data"][0]["message"] == event.message
  1648. query = {"field": ["project", "message"], "query": "!has:message", "statsPeriod": "14d"}
  1649. response = self.do_request(query, features=features)
  1650. assert response.status_code == 200, response.content
  1651. assert len(response.data["data"]) == 0
  1652. def test_has_transaction_status(self):
  1653. project = self.create_project()
  1654. data = load_data("transaction", timestamp=before_now(minutes=1))
  1655. data["transaction"] = "/transactionstatus/1"
  1656. self.store_event(data, project_id=project.id)
  1657. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1658. query = {
  1659. "field": ["event.type", "count(id)"],
  1660. "query": "event.type:transaction has:transaction.status",
  1661. "sort": "-count(id)",
  1662. "statsPeriod": "24h",
  1663. }
  1664. response = self.do_request(query, features=features)
  1665. assert response.status_code == 200, response.content
  1666. data = response.data["data"]
  1667. assert len(data) == 1
  1668. assert data[0]["count_id"] == 1
  1669. def test_not_has_transaction_status(self):
  1670. project = self.create_project()
  1671. data = load_data("transaction", timestamp=before_now(minutes=1))
  1672. data["transaction"] = "/transactionstatus/1"
  1673. self.store_event(data, project_id=project.id)
  1674. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1675. query = {
  1676. "field": ["event.type", "count(id)"],
  1677. "query": "event.type:transaction !has:transaction.status",
  1678. "sort": "-count(id)",
  1679. "statsPeriod": "24h",
  1680. }
  1681. response = self.do_request(query, features=features)
  1682. assert response.status_code == 200, response.content
  1683. data = response.data["data"]
  1684. assert len(data) == 1
  1685. assert data[0]["count_id"] == 0
  1686. def test_tag_that_looks_like_aggregation(self):
  1687. project = self.create_project()
  1688. data = {
  1689. "message": "Failure state",
  1690. "timestamp": self.two_min_ago,
  1691. "tags": {"count_diff": 99},
  1692. }
  1693. self.store_event(data, project_id=project.id)
  1694. query = {
  1695. "field": ["message", "count_diff", "count()"],
  1696. "query": "",
  1697. "project": [project.id],
  1698. "statsPeriod": "24h",
  1699. }
  1700. response = self.do_request(query)
  1701. assert response.status_code == 200, response.content
  1702. meta = response.data["meta"]
  1703. assert "string" == meta["count_diff"], "tags should not be counted as integers"
  1704. assert "string" == meta["message"]
  1705. assert "integer" == meta["count"]
  1706. assert 1 == len(response.data["data"])
  1707. data = response.data["data"][0]
  1708. assert "99" == data["count_diff"]
  1709. assert "Failure state" == data["message"]
  1710. assert 1 == data["count"]
  1711. def test_aggregate_negation(self):
  1712. project = self.create_project()
  1713. data = load_data(
  1714. "transaction",
  1715. timestamp=before_now(minutes=1),
  1716. start_timestamp=before_now(minutes=1, seconds=5),
  1717. )
  1718. self.store_event(data, project_id=project.id)
  1719. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1720. query = {
  1721. "field": ["event.type", "p99()"],
  1722. "query": "event.type:transaction p99():5s",
  1723. "statsPeriod": "24h",
  1724. }
  1725. response = self.do_request(query, features=features)
  1726. assert response.status_code == 200, response.content
  1727. data = response.data["data"]
  1728. assert len(data) == 1
  1729. query = {
  1730. "field": ["event.type", "p99()"],
  1731. "query": "event.type:transaction !p99():5s",
  1732. "statsPeriod": "24h",
  1733. }
  1734. response = self.do_request(query, features=features)
  1735. assert response.status_code == 200, response.content
  1736. data = response.data["data"]
  1737. assert len(data) == 0
  1738. def test_all_aggregates_in_columns(self):
  1739. project = self.create_project()
  1740. data = load_data(
  1741. "transaction",
  1742. timestamp=before_now(minutes=2),
  1743. start_timestamp=before_now(minutes=2, seconds=5),
  1744. )
  1745. data["transaction"] = "/failure_rate/1"
  1746. self.store_event(data, project_id=project.id)
  1747. data = load_data(
  1748. "transaction",
  1749. timestamp=before_now(minutes=1),
  1750. start_timestamp=before_now(minutes=1, seconds=5),
  1751. )
  1752. data["transaction"] = "/failure_rate/1"
  1753. data["contexts"]["trace"]["status"] = "unauthenticated"
  1754. event = self.store_event(data, project_id=project.id)
  1755. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1756. query = {
  1757. "field": [
  1758. "event.type",
  1759. "p50()",
  1760. "p75()",
  1761. "p95()",
  1762. "p99()",
  1763. "p100()",
  1764. "percentile(transaction.duration, 0.99)",
  1765. "apdex(300)",
  1766. "count_miserable(user, 300)",
  1767. "user_misery(300)",
  1768. "failure_rate()",
  1769. ],
  1770. "query": "event.type:transaction",
  1771. }
  1772. response = self.do_request(query, features=features)
  1773. assert response.status_code == 200, response.content
  1774. meta = response.data["meta"]
  1775. assert meta["p50"] == "duration"
  1776. assert meta["p75"] == "duration"
  1777. assert meta["p95"] == "duration"
  1778. assert meta["p99"] == "duration"
  1779. assert meta["p100"] == "duration"
  1780. assert meta["percentile_transaction_duration_0_99"] == "duration"
  1781. assert meta["apdex_300"] == "number"
  1782. assert meta["failure_rate"] == "percentage"
  1783. assert meta["user_misery_300"] == "number"
  1784. assert meta["count_miserable_user_300"] == "number"
  1785. data = response.data["data"]
  1786. assert len(data) == 1
  1787. assert data[0]["p50"] == 5000
  1788. assert data[0]["p75"] == 5000
  1789. assert data[0]["p95"] == 5000
  1790. assert data[0]["p99"] == 5000
  1791. assert data[0]["p100"] == 5000
  1792. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1793. assert data[0]["apdex_300"] == 0.0
  1794. assert data[0]["count_miserable_user_300"] == 1
  1795. assert data[0]["user_misery_300"] == 0.058
  1796. assert data[0]["failure_rate"] == 0.5
  1797. query = {
  1798. "field": ["event.type", "last_seen()", "latest_event()"],
  1799. "query": "event.type:transaction",
  1800. }
  1801. response = self.do_request(query, features=features)
  1802. assert response.status_code == 200, response.content
  1803. data = response.data["data"]
  1804. assert len(data) == 1
  1805. assert iso_format(before_now(minutes=1))[:-5] in data[0]["last_seen"]
  1806. assert data[0]["latest_event"] == event.event_id
  1807. query = {
  1808. "field": [
  1809. "event.type",
  1810. "count()",
  1811. "count(id)",
  1812. "count_unique(project)",
  1813. "min(transaction.duration)",
  1814. "max(transaction.duration)",
  1815. "avg(transaction.duration)",
  1816. "stddev(transaction.duration)",
  1817. "var(transaction.duration)",
  1818. "sum(transaction.duration)",
  1819. ],
  1820. "query": "event.type:transaction",
  1821. }
  1822. response = self.do_request(query, features=features)
  1823. assert response.status_code == 200, response.content
  1824. data = response.data["data"]
  1825. assert len(data) == 1
  1826. assert data[0]["count"] == 2
  1827. assert data[0]["count_id"] == 2
  1828. assert data[0]["count_unique_project"] == 1
  1829. assert data[0]["min_transaction_duration"] == 5000
  1830. assert data[0]["max_transaction_duration"] == 5000
  1831. assert data[0]["avg_transaction_duration"] == 5000
  1832. assert data[0]["stddev_transaction_duration"] == 0.0
  1833. assert data[0]["var_transaction_duration"] == 0.0
  1834. assert data[0]["sum_transaction_duration"] == 10000
  1835. def test_null_user_misery_returns_zero(self):
  1836. project = self.create_project()
  1837. data = load_data(
  1838. "transaction",
  1839. timestamp=before_now(minutes=2),
  1840. start_timestamp=before_now(minutes=2, seconds=5),
  1841. )
  1842. data["user"] = None
  1843. data["transaction"] = "/no_users/1"
  1844. self.store_event(data, project_id=project.id)
  1845. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1846. query = {
  1847. "field": ["user_misery(300)"],
  1848. "query": "event.type:transaction",
  1849. }
  1850. response = self.do_request(query, features=features)
  1851. assert response.status_code == 200, response.content
  1852. meta = response.data["meta"]
  1853. assert meta["user_misery_300"] == "number"
  1854. data = response.data["data"]
  1855. assert data[0]["user_misery_300"] == 0
  1856. def test_all_aggregates_in_query(self):
  1857. project = self.create_project()
  1858. data = load_data(
  1859. "transaction",
  1860. timestamp=before_now(minutes=2),
  1861. start_timestamp=before_now(minutes=2, seconds=5),
  1862. )
  1863. data["transaction"] = "/failure_rate/1"
  1864. self.store_event(data, project_id=project.id)
  1865. data = load_data(
  1866. "transaction",
  1867. timestamp=before_now(minutes=1),
  1868. start_timestamp=before_now(minutes=1, seconds=5),
  1869. )
  1870. data["transaction"] = "/failure_rate/2"
  1871. data["contexts"]["trace"]["status"] = "unauthenticated"
  1872. self.store_event(data, project_id=project.id)
  1873. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1874. query = {
  1875. "field": [
  1876. "event.type",
  1877. "p50()",
  1878. "p75()",
  1879. "p95()",
  1880. "percentile(transaction.duration, 0.99)",
  1881. "p100()",
  1882. ],
  1883. "query": "event.type:transaction p50():>100 p75():>1000 p95():>1000 p100():>1000 percentile(transaction.duration, 0.99):>1000",
  1884. }
  1885. response = self.do_request(query, features=features)
  1886. assert response.status_code == 200, response.content
  1887. data = response.data["data"]
  1888. assert len(data) == 1
  1889. assert data[0]["p50"] == 5000
  1890. assert data[0]["p75"] == 5000
  1891. assert data[0]["p95"] == 5000
  1892. assert data[0]["p100"] == 5000
  1893. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1894. query = {
  1895. "field": [
  1896. "event.type",
  1897. "apdex(300)",
  1898. "count_miserable(user, 300)",
  1899. "user_misery(300)",
  1900. "failure_rate()",
  1901. ],
  1902. "query": "event.type:transaction apdex(300):>-1.0 failure_rate():>0.25",
  1903. }
  1904. response = self.do_request(query, features=features)
  1905. assert response.status_code == 200, response.content
  1906. data = response.data["data"]
  1907. assert len(data) == 1
  1908. assert data[0]["apdex_300"] == 0.0
  1909. assert data[0]["count_miserable_user_300"] == 1
  1910. assert data[0]["user_misery_300"] == 0.058
  1911. assert data[0]["failure_rate"] == 0.5
  1912. query = {
  1913. "field": ["event.type", "last_seen()", "latest_event()"],
  1914. "query": "event.type:transaction last_seen():>1990-12-01T00:00:00",
  1915. }
  1916. response = self.do_request(query, features=features)
  1917. assert response.status_code == 200, response.content
  1918. data = response.data["data"]
  1919. assert len(data) == 1
  1920. query = {
  1921. "field": ["event.type", "count()", "count(id)", "count_unique(transaction)"],
  1922. "query": "event.type:transaction count():>1 count(id):>1 count_unique(transaction):>1",
  1923. }
  1924. response = self.do_request(query, features=features)
  1925. assert response.status_code == 200, response.content
  1926. data = response.data["data"]
  1927. assert len(data) == 1
  1928. assert data[0]["count"] == 2
  1929. assert data[0]["count_id"] == 2
  1930. assert data[0]["count_unique_transaction"] == 2
  1931. query = {
  1932. "field": [
  1933. "event.type",
  1934. "min(transaction.duration)",
  1935. "max(transaction.duration)",
  1936. "avg(transaction.duration)",
  1937. "sum(transaction.duration)",
  1938. "stddev(transaction.duration)",
  1939. "var(transaction.duration)",
  1940. ],
  1941. "query": " ".join(
  1942. [
  1943. "event.type:transaction",
  1944. "min(transaction.duration):>1000",
  1945. "max(transaction.duration):>1000",
  1946. "avg(transaction.duration):>1000",
  1947. "sum(transaction.duration):>1000",
  1948. "stddev(transaction.duration):>=0.0",
  1949. "var(transaction.duration):>=0.0",
  1950. ]
  1951. ),
  1952. }
  1953. response = self.do_request(query, features=features)
  1954. assert response.status_code == 200, response.content
  1955. data = response.data["data"]
  1956. assert len(data) == 1
  1957. assert data[0]["min_transaction_duration"] == 5000
  1958. assert data[0]["max_transaction_duration"] == 5000
  1959. assert data[0]["avg_transaction_duration"] == 5000
  1960. assert data[0]["stddev_transaction_duration"] == 0.0
  1961. assert data[0]["var_transaction_duration"] == 0.0
  1962. assert data[0]["sum_transaction_duration"] == 10000
  1963. query = {
  1964. "field": ["event.type", "apdex(400)"],
  1965. "query": "event.type:transaction apdex(400):0",
  1966. }
  1967. response = self.do_request(query, features=features)
  1968. assert response.status_code == 200, response.content
  1969. data = response.data["data"]
  1970. assert len(data) == 1
  1971. assert data[0]["apdex_400"] == 0
  1972. def test_functions_in_orderby(self):
  1973. project = self.create_project()
  1974. data = load_data(
  1975. "transaction",
  1976. timestamp=before_now(minutes=2),
  1977. start_timestamp=before_now(minutes=2, seconds=5),
  1978. )
  1979. data["transaction"] = "/failure_rate/1"
  1980. self.store_event(data, project_id=project.id)
  1981. data = load_data(
  1982. "transaction",
  1983. timestamp=before_now(minutes=1),
  1984. start_timestamp=before_now(minutes=1, seconds=5),
  1985. )
  1986. data["transaction"] = "/failure_rate/2"
  1987. data["contexts"]["trace"]["status"] = "unauthenticated"
  1988. event = self.store_event(data, project_id=project.id)
  1989. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1990. query = {
  1991. "field": ["event.type", "p75()"],
  1992. "sort": "-p75",
  1993. "query": "event.type:transaction",
  1994. }
  1995. response = self.do_request(query, features=features)
  1996. assert response.status_code == 200, response.content
  1997. data = response.data["data"]
  1998. assert len(data) == 1
  1999. assert data[0]["p75"] == 5000
  2000. query = {
  2001. "field": ["event.type", "percentile(transaction.duration, 0.99)"],
  2002. "sort": "-percentile_transaction_duration_0_99",
  2003. "query": "event.type:transaction",
  2004. }
  2005. response = self.do_request(query, features=features)
  2006. assert response.status_code == 200, response.content
  2007. data = response.data["data"]
  2008. assert len(data) == 1
  2009. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  2010. query = {
  2011. "field": ["event.type", "apdex(300)"],
  2012. "sort": "-apdex(300)",
  2013. "query": "event.type:transaction",
  2014. }
  2015. response = self.do_request(query, features=features)
  2016. assert response.status_code == 200, response.content
  2017. data = response.data["data"]
  2018. assert len(data) == 1
  2019. assert data[0]["apdex_300"] == 0.0
  2020. query = {
  2021. "field": ["event.type", "latest_event()"],
  2022. "query": "event.type:transaction",
  2023. "sort": "latest_event",
  2024. }
  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]["latest_event"] == event.event_id
  2030. query = {
  2031. "field": ["event.type", "count_unique(transaction)"],
  2032. "query": "event.type:transaction",
  2033. "sort": "-count_unique_transaction",
  2034. }
  2035. response = self.do_request(query, features=features)
  2036. assert response.status_code == 200, response.content
  2037. data = response.data["data"]
  2038. assert len(data) == 1
  2039. assert data[0]["count_unique_transaction"] == 2
  2040. query = {
  2041. "field": ["event.type", "min(transaction.duration)"],
  2042. "query": "event.type:transaction",
  2043. "sort": "-min_transaction_duration",
  2044. }
  2045. response = self.do_request(query, features=features)
  2046. assert response.status_code == 200, response.content
  2047. data = response.data["data"]
  2048. assert len(data) == 1
  2049. assert data[0]["min_transaction_duration"] == 5000
  2050. def test_issue_alias_in_aggregate(self):
  2051. project = self.create_project()
  2052. self.store_event(
  2053. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2054. project_id=project.id,
  2055. )
  2056. self.store_event(
  2057. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  2058. project_id=project.id,
  2059. )
  2060. query = {"field": ["event.type", "count_unique(issue)"], "query": "count_unique(issue):>1"}
  2061. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2062. response = self.do_request(query, features=features)
  2063. assert response.status_code == 200, response.content
  2064. data = response.data["data"]
  2065. assert len(data) == 1
  2066. assert data[0]["count_unique_issue"] == 2
  2067. def test_deleted_issue_in_results(self):
  2068. project = self.create_project()
  2069. event1 = self.store_event(
  2070. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2071. project_id=project.id,
  2072. )
  2073. event2 = self.store_event(
  2074. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  2075. project_id=project.id,
  2076. )
  2077. event2.group.delete()
  2078. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2079. query = {"field": ["issue", "count()"], "sort": "issue"}
  2080. response = self.do_request(query, features=features)
  2081. assert response.status_code == 200, response.content
  2082. data = response.data["data"]
  2083. assert len(data) == 2
  2084. assert data[0]["issue"] == event1.group.qualified_short_id
  2085. assert data[1]["issue"] == "unknown"
  2086. def test_last_seen_negative_duration(self):
  2087. project = self.create_project()
  2088. self.store_event(
  2089. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2090. project_id=project.id,
  2091. )
  2092. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2093. query = {"field": ["id", "last_seen()"], "query": "last_seen():-30d"}
  2094. response = self.do_request(query, features=features)
  2095. assert response.status_code == 200, response.content
  2096. data = response.data["data"]
  2097. assert len(data) == 1
  2098. assert data[0]["id"] == "f" * 32
  2099. def test_last_seen_aggregate_condition(self):
  2100. project = self.create_project()
  2101. self.store_event(
  2102. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  2103. project_id=project.id,
  2104. )
  2105. query = {
  2106. "field": ["id", "last_seen()"],
  2107. "query": f"last_seen():>{iso_format(before_now(days=30))}",
  2108. }
  2109. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2110. response = self.do_request(query, features=features)
  2111. assert response.status_code == 200, response.content
  2112. data = response.data["data"]
  2113. assert len(data) == 1
  2114. assert data[0]["id"] == "f" * 32
  2115. def test_conditional_filter(self):
  2116. project = self.create_project()
  2117. for v in ["a", "b"]:
  2118. self.store_event(
  2119. data={
  2120. "event_id": v * 32,
  2121. "timestamp": self.two_min_ago,
  2122. "fingerprint": ["group_1"],
  2123. },
  2124. project_id=project.id,
  2125. )
  2126. query = {
  2127. "field": ["id"],
  2128. "query": "id:{} OR id:{}".format("a" * 32, "b" * 32),
  2129. "orderby": "id",
  2130. }
  2131. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2132. response = self.do_request(query, features=features)
  2133. assert response.status_code == 200, response.content
  2134. data = response.data["data"]
  2135. assert len(data) == 2
  2136. assert data[0]["id"] == "a" * 32
  2137. assert data[1]["id"] == "b" * 32
  2138. def test_aggregation_comparison_with_conditional_filter(self):
  2139. project = self.create_project()
  2140. self.store_event(
  2141. data={
  2142. "event_id": "a" * 32,
  2143. "timestamp": self.min_ago,
  2144. "fingerprint": ["group_1"],
  2145. "user": {"email": "foo@example.com"},
  2146. "environment": "prod",
  2147. },
  2148. project_id=project.id,
  2149. )
  2150. self.store_event(
  2151. data={
  2152. "event_id": "b" * 32,
  2153. "timestamp": self.min_ago,
  2154. "fingerprint": ["group_2"],
  2155. "user": {"email": "foo@example.com"},
  2156. "environment": "staging",
  2157. },
  2158. project_id=project.id,
  2159. )
  2160. event = self.store_event(
  2161. data={
  2162. "event_id": "c" * 32,
  2163. "timestamp": self.min_ago,
  2164. "fingerprint": ["group_2"],
  2165. "user": {"email": "foo@example.com"},
  2166. "environment": "prod",
  2167. },
  2168. project_id=project.id,
  2169. )
  2170. self.store_event(
  2171. data={
  2172. "event_id": "d" * 32,
  2173. "timestamp": self.min_ago,
  2174. "fingerprint": ["group_2"],
  2175. "user": {"email": "foo@example.com"},
  2176. "environment": "canary",
  2177. },
  2178. project_id=project.id,
  2179. )
  2180. query = {
  2181. "field": ["issue.id", "count(id)"],
  2182. "query": "count(id):>1 user.email:foo@example.com AND (environment:prod OR environment:staging)",
  2183. "orderby": "issue.id",
  2184. }
  2185. response = self.do_request(query)
  2186. assert response.status_code == 200, response.content
  2187. assert len(response.data["data"]) == 1
  2188. data = response.data["data"]
  2189. assert data[0]["issue.id"] == event.group_id
  2190. assert data[0]["count_id"] == 2
  2191. def run_test_in_query(self, query, expected_events, expected_negative_events=None):
  2192. params = {
  2193. "field": ["id"],
  2194. "query": query,
  2195. "orderby": "id",
  2196. }
  2197. response = self.do_request(
  2198. params, {"organizations:discover-basic": True, "organizations:global-views": True}
  2199. )
  2200. assert response.status_code == 200, response.content
  2201. assert [row["id"] for row in response.data["data"]] == [e.event_id for e in expected_events]
  2202. if expected_negative_events is not None:
  2203. params["query"] = f"!{query}"
  2204. response = self.do_request(
  2205. params,
  2206. {"organizations:discover-basic": True, "organizations:global-views": True},
  2207. )
  2208. assert response.status_code == 200, response.content
  2209. assert [row["id"] for row in response.data["data"]] == [
  2210. e.event_id for e in expected_negative_events
  2211. ]
  2212. def test_in_query_events(self):
  2213. project_1 = self.create_project()
  2214. event_1 = self.store_event(
  2215. data={
  2216. "event_id": "a" * 32,
  2217. "timestamp": self.min_ago,
  2218. "fingerprint": ["group_1"],
  2219. "message": "group1",
  2220. "user": {"email": "hello@example.com"},
  2221. "environment": "prod",
  2222. "tags": {"random": "123"},
  2223. "release": "1.0",
  2224. },
  2225. project_id=project_1.id,
  2226. )
  2227. project_2 = self.create_project()
  2228. event_2 = self.store_event(
  2229. data={
  2230. "event_id": "b" * 32,
  2231. "timestamp": self.min_ago,
  2232. "fingerprint": ["group_2"],
  2233. "message": "group2",
  2234. "user": {"email": "bar@example.com"},
  2235. "environment": "staging",
  2236. "tags": {"random": "456"},
  2237. "stacktrace": {"frames": [{"filename": "src/app/group2.py"}]},
  2238. "release": "1.2",
  2239. },
  2240. project_id=project_2.id,
  2241. )
  2242. project_3 = self.create_project()
  2243. event_3 = self.store_event(
  2244. data={
  2245. "event_id": "c" * 32,
  2246. "timestamp": self.min_ago,
  2247. "fingerprint": ["group_3"],
  2248. "message": "group3",
  2249. "user": {"email": "foo@example.com"},
  2250. "environment": "canary",
  2251. "tags": {"random": "789"},
  2252. },
  2253. project_id=project_3.id,
  2254. )
  2255. self.run_test_in_query("environment:[prod, staging]", [event_1, event_2], [event_3])
  2256. self.run_test_in_query("environment:[staging]", [event_2], [event_1, event_3])
  2257. self.run_test_in_query(
  2258. "user.email:[foo@example.com, hello@example.com]", [event_1, event_3], [event_2]
  2259. )
  2260. self.run_test_in_query("user.email:[foo@example.com]", [event_3], [event_1, event_2])
  2261. self.run_test_in_query(
  2262. "user.display:[foo@example.com, hello@example.com]", [event_1, event_3], [event_2]
  2263. )
  2264. self.run_test_in_query("message:[group2, group1]", [event_1, event_2], [event_3])
  2265. self.run_test_in_query(
  2266. f"issue.id:[{event_1.group_id},{event_2.group_id}]", [event_1, event_2]
  2267. )
  2268. self.run_test_in_query(
  2269. f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}]",
  2270. [event_1, event_2],
  2271. )
  2272. self.run_test_in_query(
  2273. f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}, unknown]",
  2274. [event_1, event_2],
  2275. )
  2276. self.run_test_in_query(f"project_id:[{project_3.id},{project_2.id}]", [event_2, event_3])
  2277. self.run_test_in_query(
  2278. f"project.name:[{project_3.slug},{project_2.slug}]", [event_2, event_3]
  2279. )
  2280. self.run_test_in_query("random:[789,456]", [event_2, event_3], [event_1])
  2281. self.run_test_in_query("tags[random]:[789,456]", [event_2, event_3], [event_1])
  2282. self.run_test_in_query("release:[1.0,1.2]", [event_1, event_2], [event_3])
  2283. def test_in_query_events_stack(self):
  2284. project_1 = self.create_project()
  2285. test_js = self.store_event(
  2286. load_data(
  2287. "javascript",
  2288. timestamp=before_now(minutes=1),
  2289. start_timestamp=before_now(minutes=1, seconds=5),
  2290. ),
  2291. project_id=project_1.id,
  2292. )
  2293. test_java = self.store_event(
  2294. load_data(
  2295. "java",
  2296. timestamp=before_now(minutes=1),
  2297. start_timestamp=before_now(minutes=1, seconds=5),
  2298. ),
  2299. project_id=project_1.id,
  2300. )
  2301. self.run_test_in_query(
  2302. "stack.filename:[../../sentry/scripts/views.js]", [test_js], [test_java]
  2303. )
  2304. def test_in_query_transactions(self):
  2305. project = self.create_project()
  2306. data = load_data(
  2307. "transaction",
  2308. timestamp=before_now(minutes=1),
  2309. start_timestamp=before_now(minutes=1, seconds=5),
  2310. )
  2311. data["event_id"] = "a" * 32
  2312. data["contexts"]["trace"]["status"] = "ok"
  2313. transaction_1 = self.store_event(data, project_id=project.id)
  2314. data = load_data(
  2315. "transaction",
  2316. timestamp=before_now(minutes=1),
  2317. start_timestamp=before_now(minutes=1, seconds=5),
  2318. )
  2319. data["event_id"] = "b" * 32
  2320. data["contexts"]["trace"]["status"] = "aborted"
  2321. transaction_2 = self.store_event(data, project_id=project.id)
  2322. data = load_data(
  2323. "transaction",
  2324. timestamp=before_now(minutes=1),
  2325. start_timestamp=before_now(minutes=1, seconds=5),
  2326. )
  2327. data["event_id"] = "c" * 32
  2328. data["contexts"]["trace"]["status"] = "already_exists"
  2329. transaction_3 = self.store_event(data, project_id=project.id)
  2330. self.run_test_in_query(
  2331. "transaction.status:[aborted, already_exists]",
  2332. [transaction_2, transaction_3],
  2333. [transaction_1],
  2334. )
  2335. def test_messed_up_function_values(self):
  2336. # TODO (evanh): It would be nice if this surfaced an error to the user.
  2337. # The problem: The && causes the parser to treat that term not as a bad
  2338. # function call but a valid raw search with parens in it. It's not trivial
  2339. # to change the parser to recognize "bad function values" and surface them.
  2340. project = self.create_project()
  2341. for v in ["a", "b"]:
  2342. self.store_event(
  2343. data={
  2344. "event_id": v * 32,
  2345. "timestamp": self.two_min_ago,
  2346. "fingerprint": ["group_1"],
  2347. },
  2348. project_id=project.id,
  2349. )
  2350. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2351. query = {
  2352. "field": [
  2353. "transaction",
  2354. "project",
  2355. "epm()",
  2356. "p50()",
  2357. "p95()",
  2358. "failure_rate()",
  2359. "apdex(300)",
  2360. "count_unique(user)",
  2361. "user_misery(300)",
  2362. "count_miserable(user, 300)",
  2363. ],
  2364. "query": "failure_rate():>0.003&& users:>10 event.type:transaction",
  2365. "sort": "-failure_rate",
  2366. "statsPeriod": "24h",
  2367. }
  2368. response = self.do_request(query, features=features)
  2369. assert response.status_code == 200, response.content
  2370. data = response.data["data"]
  2371. assert len(data) == 0
  2372. def test_context_fields_between_datasets(self):
  2373. project = self.create_project()
  2374. event_data = load_data("android")
  2375. transaction_data = load_data("transaction")
  2376. event_data["spans"] = transaction_data["spans"]
  2377. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  2378. event_data["type"] = "transaction"
  2379. event_data["transaction"] = "/failure_rate/1"
  2380. event_data["timestamp"] = iso_format(before_now(minutes=1))
  2381. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  2382. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  2383. self.store_event(event_data, project_id=project.id)
  2384. event_data["type"] = "error"
  2385. self.store_event(event_data, project_id=project.id)
  2386. fields = [
  2387. "os.build",
  2388. "os.kernel_version",
  2389. "device.arch",
  2390. # TODO: battery level is not consistent across both datasets
  2391. # "device.battery_level",
  2392. "device.brand",
  2393. "device.charging",
  2394. "device.locale",
  2395. "device.model_id",
  2396. "device.name",
  2397. "device.online",
  2398. "device.orientation",
  2399. "device.simulator",
  2400. "device.uuid",
  2401. ]
  2402. data = [
  2403. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  2404. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  2405. ]
  2406. for datum in data:
  2407. response = self.do_request(datum)
  2408. assert response.status_code == 200, response.content
  2409. assert len(response.data["data"]) == 1, datum
  2410. results = response.data["data"]
  2411. assert results[0]["count"] == 1, datum
  2412. for field in fields:
  2413. key, value = field.split(".", 1)
  2414. expected = str(event_data["contexts"][key][value])
  2415. assert results[0][field] == expected, field + str(datum)
  2416. def test_http_fields_between_datasets(self):
  2417. project = self.create_project()
  2418. event_data = load_data("android")
  2419. transaction_data = load_data("transaction")
  2420. event_data["spans"] = transaction_data["spans"]
  2421. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  2422. event_data["type"] = "transaction"
  2423. event_data["transaction"] = "/failure_rate/1"
  2424. event_data["timestamp"] = iso_format(before_now(minutes=1))
  2425. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  2426. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  2427. event_data["request"] = transaction_data["request"]
  2428. self.store_event(event_data, project_id=project.id)
  2429. event_data["type"] = "error"
  2430. self.store_event(event_data, project_id=project.id)
  2431. fields = ["http.method", "http.referer", "http.url"]
  2432. expected = ["GET", "fixtures.transaction", "http://countries:8010/country_by_code/"]
  2433. data = [
  2434. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  2435. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  2436. ]
  2437. for datum in data:
  2438. response = self.do_request(datum)
  2439. assert response.status_code == 200, response.content
  2440. assert len(response.data["data"]) == 1, datum
  2441. results = response.data["data"]
  2442. assert results[0]["count"] == 1, datum
  2443. for (field, exp) in zip(fields, expected):
  2444. assert results[0][field] == exp, field + str(datum)
  2445. def test_failure_count_alias_field(self):
  2446. project = self.create_project()
  2447. data = load_data("transaction", timestamp=before_now(minutes=1))
  2448. data["transaction"] = "/failure_count/success"
  2449. self.store_event(data, project_id=project.id)
  2450. data = load_data("transaction", timestamp=before_now(minutes=1))
  2451. data["transaction"] = "/failure_count/unknown"
  2452. data["contexts"]["trace"]["status"] = "unknown_error"
  2453. self.store_event(data, project_id=project.id)
  2454. for i in range(6):
  2455. data = load_data("transaction", timestamp=before_now(minutes=1))
  2456. data["transaction"] = f"/failure_count/{i}"
  2457. data["contexts"]["trace"]["status"] = "unauthenticated"
  2458. self.store_event(data, project_id=project.id)
  2459. query = {"field": ["count()", "failure_count()"], "query": "event.type:transaction"}
  2460. response = self.do_request(query)
  2461. assert response.status_code == 200, response.content
  2462. assert len(response.data["data"]) == 1
  2463. data = response.data["data"]
  2464. assert data[0]["count"] == 8
  2465. assert data[0]["failure_count"] == 6
  2466. @mock.patch("sentry.utils.snuba.quantize_time")
  2467. def test_quantize_dates(self, mock_quantize):
  2468. self.create_project()
  2469. mock_quantize.return_value = before_now(days=1).replace(tzinfo=utc)
  2470. # Don't quantize short time periods
  2471. query = {"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]}
  2472. self.do_request(query)
  2473. # Don't quantize absolute date periods
  2474. self.do_request(query)
  2475. query = {
  2476. "start": iso_format(before_now(days=20)),
  2477. "end": iso_format(before_now(days=15)),
  2478. "query": "",
  2479. "field": ["id", "timestamp"],
  2480. }
  2481. self.do_request(query)
  2482. assert len(mock_quantize.mock_calls) == 0
  2483. # Quantize long date periods
  2484. query = {"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""}
  2485. self.do_request(query)
  2486. assert len(mock_quantize.mock_calls) == 2
  2487. @mock.patch("sentry.snuba.discover.query")
  2488. def test_valid_referrer(self, mock):
  2489. mock.return_value = {}
  2490. project = self.create_project()
  2491. data = load_data("transaction", timestamp=before_now(hours=1))
  2492. self.store_event(data=data, project_id=project.id)
  2493. query = {
  2494. "field": ["user"],
  2495. "referrer": "api.performance.transaction-summary",
  2496. }
  2497. self.do_request(query)
  2498. _, kwargs = mock.call_args
  2499. self.assertEqual(kwargs["referrer"], "api.performance.transaction-summary")
  2500. @mock.patch("sentry.snuba.discover.query")
  2501. def test_invalid_referrer(self, mock):
  2502. mock.return_value = {}
  2503. project = self.create_project()
  2504. data = load_data("transaction", timestamp=before_now(hours=1))
  2505. self.store_event(data=data, project_id=project.id)
  2506. query = {
  2507. "field": ["user"],
  2508. "referrer": "api.performance.invalid",
  2509. }
  2510. self.do_request(query)
  2511. _, kwargs = mock.call_args
  2512. self.assertEqual(kwargs["referrer"], "api.organization-events-v2")
  2513. @mock.patch("sentry.snuba.discover.query")
  2514. def test_empty_referrer(self, mock):
  2515. mock.return_value = {}
  2516. project = self.create_project()
  2517. data = load_data("transaction", timestamp=before_now(hours=1))
  2518. self.store_event(data=data, project_id=project.id)
  2519. query = {
  2520. "field": ["user"],
  2521. }
  2522. self.do_request(query)
  2523. _, kwargs = mock.call_args
  2524. self.assertEqual(kwargs["referrer"], "api.organization-events-v2")
  2525. def test_limit_number_of_fields(self):
  2526. self.create_project()
  2527. for i in range(1, 25):
  2528. response = self.do_request({"field": ["id"] * i})
  2529. if i <= 20:
  2530. assert response.status_code == 200
  2531. else:
  2532. assert response.status_code == 400
  2533. assert (
  2534. response.data["detail"]
  2535. == "You can view up to 20 fields at a time. Please delete some and try again."
  2536. )
  2537. def test_percentile_function_meta_types(self):
  2538. project = self.create_project()
  2539. data = load_data(
  2540. "transaction",
  2541. timestamp=before_now(minutes=1),
  2542. start_timestamp=before_now(minutes=1, seconds=5),
  2543. )
  2544. self.store_event(data, project_id=project.id)
  2545. query = {
  2546. "field": [
  2547. "transaction",
  2548. "percentile(transaction.duration, 0.95)",
  2549. "percentile(measurements.fp, 0.95)",
  2550. "percentile(measurements.fcp, 0.95)",
  2551. "percentile(measurements.lcp, 0.95)",
  2552. "percentile(measurements.fid, 0.95)",
  2553. "percentile(measurements.ttfb, 0.95)",
  2554. "percentile(measurements.ttfb.requesttime, 0.95)",
  2555. "percentile(measurements.cls, 0.95)",
  2556. "percentile(measurements.foo, 0.95)",
  2557. "percentile(measurements.bar, 0.95)",
  2558. ],
  2559. "query": "",
  2560. "orderby": ["transaction"],
  2561. }
  2562. response = self.do_request(query)
  2563. assert response.status_code == 200, response.content
  2564. meta = response.data["meta"]
  2565. assert meta["percentile_transaction_duration_0_95"] == "duration"
  2566. assert meta["percentile_measurements_fp_0_95"] == "duration"
  2567. assert meta["percentile_measurements_fcp_0_95"] == "duration"
  2568. assert meta["percentile_measurements_lcp_0_95"] == "duration"
  2569. assert meta["percentile_measurements_fid_0_95"] == "duration"
  2570. assert meta["percentile_measurements_ttfb_0_95"] == "duration"
  2571. assert meta["percentile_measurements_ttfb_requesttime_0_95"] == "duration"
  2572. assert meta["percentile_measurements_cls_0_95"] == "number"
  2573. assert meta["percentile_measurements_foo_0_95"] == "number"
  2574. assert meta["percentile_measurements_bar_0_95"] == "number"
  2575. def test_count_at_least_query(self):
  2576. self.store_event(self.transaction_data, self.project.id)
  2577. response = self.do_request({"field": "count_at_least(measurements.fcp, 0)"})
  2578. assert response.status_code == 200
  2579. assert len(response.data["data"]) == 1
  2580. assert response.data["data"][0]["count_at_least_measurements_fcp_0"] == 1
  2581. # a value that's a little bigger than the stored fcp
  2582. fcp = int(self.transaction_data["measurements"]["fcp"]["value"] + 1)
  2583. response = self.do_request({"field": f"count_at_least(measurements.fcp, {fcp})"})
  2584. assert response.status_code == 200
  2585. assert len(response.data["data"]) == 1
  2586. assert response.data["data"][0][f"count_at_least_measurements_fcp_{fcp}"] == 0
  2587. def test_measurements_query(self):
  2588. self.store_event(self.transaction_data, self.project.id)
  2589. query = {
  2590. "field": [
  2591. "measurements.fp",
  2592. "measurements.fcp",
  2593. "measurements.lcp",
  2594. "measurements.fid",
  2595. ]
  2596. }
  2597. response = self.do_request(query)
  2598. assert response.status_code == 200, response.content
  2599. assert len(response.data["data"]) == 1
  2600. for field in query["field"]:
  2601. measure = field.split(".", 1)[1]
  2602. assert (
  2603. response.data["data"][0][field]
  2604. == self.transaction_data["measurements"][measure]["value"]
  2605. )
  2606. query = {
  2607. "field": [
  2608. "measurements.fP",
  2609. "measurements.Fcp",
  2610. "measurements.LcP",
  2611. "measurements.FID",
  2612. ]
  2613. }
  2614. response = self.do_request(query)
  2615. assert response.status_code == 200, response.content
  2616. assert len(response.data["data"]) == 1
  2617. for field in query["field"]:
  2618. measure = field.split(".", 1)[1].lower()
  2619. assert (
  2620. response.data["data"][0][field]
  2621. == self.transaction_data["measurements"][measure]["value"]
  2622. )
  2623. def test_measurements_aggregations(self):
  2624. self.store_event(self.transaction_data, self.project.id)
  2625. # should try all the potential aggregates
  2626. # Skipped tests for stddev and var since sampling one data point
  2627. # results in nan.
  2628. query = {
  2629. "field": [
  2630. "percentile(measurements.fcp, 0.5)",
  2631. "count_unique(measurements.fcp)",
  2632. "min(measurements.fcp)",
  2633. "max(measurements.fcp)",
  2634. "avg(measurements.fcp)",
  2635. "sum(measurements.fcp)",
  2636. ],
  2637. }
  2638. response = self.do_request(query)
  2639. assert response.status_code == 200, response.content
  2640. assert len(response.data["data"]) == 1
  2641. assert (
  2642. response.data["data"][0]["percentile_measurements_fcp_0_5"]
  2643. == self.transaction_data["measurements"]["fcp"]["value"]
  2644. )
  2645. assert response.data["data"][0]["count_unique_measurements_fcp"] == 1
  2646. assert (
  2647. response.data["data"][0]["min_measurements_fcp"]
  2648. == self.transaction_data["measurements"]["fcp"]["value"]
  2649. )
  2650. assert (
  2651. response.data["data"][0]["max_measurements_fcp"]
  2652. == self.transaction_data["measurements"]["fcp"]["value"]
  2653. )
  2654. assert (
  2655. response.data["data"][0]["avg_measurements_fcp"]
  2656. == self.transaction_data["measurements"]["fcp"]["value"]
  2657. )
  2658. assert (
  2659. response.data["data"][0]["sum_measurements_fcp"]
  2660. == self.transaction_data["measurements"]["fcp"]["value"]
  2661. )
  2662. def get_measurement_condition_response(self, query_str, field):
  2663. query = {
  2664. "field": ["transaction", "count()"] + (field if field else []),
  2665. "query": query_str,
  2666. }
  2667. response = self.do_request(query)
  2668. assert response.status_code == 200, response.content
  2669. return response
  2670. def assert_measurement_condition_without_results(self, query_str, field=None):
  2671. response = self.get_measurement_condition_response(query_str, field)
  2672. assert len(response.data["data"]) == 0
  2673. def assert_measurement_condition_with_results(self, query_str, field=None):
  2674. response = self.get_measurement_condition_response(query_str, field)
  2675. assert len(response.data["data"]) == 1
  2676. assert response.data["data"][0]["transaction"] == self.transaction_data["metadata"]["title"]
  2677. assert response.data["data"][0]["count"] == 1
  2678. def test_measurements_conditions(self):
  2679. self.store_event(self.transaction_data, self.project.id)
  2680. fcp = self.transaction_data["measurements"]["fcp"]["value"]
  2681. # equality condition
  2682. # We use json dumps here to ensure precision when converting from float to str
  2683. # This is necessary because equality on floating point values need to be precise
  2684. self.assert_measurement_condition_with_results(f"measurements.fcp:{json.dumps(fcp)}")
  2685. # greater than condition
  2686. self.assert_measurement_condition_with_results(f"measurements.fcp:>{fcp - 1}")
  2687. self.assert_measurement_condition_without_results(f"measurements.fcp:>{fcp + 1}")
  2688. # less than condition
  2689. self.assert_measurement_condition_with_results(f"measurements.fcp:<{fcp + 1}")
  2690. self.assert_measurement_condition_without_results(f"measurements.fcp:<{fcp - 1}")
  2691. # has condition
  2692. self.assert_measurement_condition_with_results("has:measurements.fcp")
  2693. self.assert_measurement_condition_without_results("!has:measurements.fcp")
  2694. def test_measurements_aggregation_conditions(self):
  2695. self.store_event(self.transaction_data, self.project.id)
  2696. fcp = self.transaction_data["measurements"]["fcp"]["value"]
  2697. functions = [
  2698. "percentile(measurements.fcp, 0.5)",
  2699. "min(measurements.fcp)",
  2700. "max(measurements.fcp)",
  2701. "avg(measurements.fcp)",
  2702. "sum(measurements.fcp)",
  2703. ]
  2704. for function in functions:
  2705. self.assert_measurement_condition_with_results(
  2706. f"{function}:>{fcp - 1}", field=[function]
  2707. )
  2708. self.assert_measurement_condition_without_results(
  2709. f"{function}:>{fcp + 1}", field=[function]
  2710. )
  2711. self.assert_measurement_condition_with_results(
  2712. f"{function}:<{fcp + 1}", field=[function]
  2713. )
  2714. self.assert_measurement_condition_without_results(
  2715. f"{function}:<{fcp - 1}", field=[function]
  2716. )
  2717. count_unique = "count_unique(measurements.fcp)"
  2718. self.assert_measurement_condition_with_results(f"{count_unique}:1", field=[count_unique])
  2719. self.assert_measurement_condition_without_results(f"{count_unique}:0", field=[count_unique])
  2720. def test_compare_numeric_aggregate(self):
  2721. self.store_event(self.transaction_data, self.project.id)
  2722. query = {
  2723. "field": [
  2724. "p75(measurements.fcp)",
  2725. "compare_numeric_aggregate(p75_measurements_fcp,greater,0)",
  2726. ],
  2727. }
  2728. response = self.do_request(query)
  2729. assert response.status_code == 200, response.content
  2730. assert len(response.data["data"]) == 1
  2731. assert (
  2732. response.data["data"][0]["compare_numeric_aggregate_p75_measurements_fcp_greater_0"]
  2733. == 1
  2734. )
  2735. query = {
  2736. "field": ["p75()", "compare_numeric_aggregate(p75,equals,0)"],
  2737. }
  2738. response = self.do_request(query)
  2739. assert response.status_code == 200, response.content
  2740. assert len(response.data["data"]) == 1
  2741. assert response.data["data"][0]["compare_numeric_aggregate_p75_equals_0"] == 0
  2742. def test_no_key_transactions(self):
  2743. transactions = [
  2744. "/blah_transaction/",
  2745. "/foo_transaction/",
  2746. "/zoo_transaction/",
  2747. ]
  2748. for transaction in transactions:
  2749. self.transaction_data["transaction"] = transaction
  2750. self.store_event(self.transaction_data, self.project.id)
  2751. query = {
  2752. "project": [self.project.id],
  2753. # use the order by to ensure the result order
  2754. "orderby": "transaction",
  2755. "field": [
  2756. "key_transaction",
  2757. "transaction",
  2758. "transaction.status",
  2759. "project",
  2760. "epm()",
  2761. "failure_rate()",
  2762. "percentile(transaction.duration, 0.95)",
  2763. ],
  2764. }
  2765. response = self.do_request(query)
  2766. assert response.status_code == 200, response.content
  2767. data = response.data["data"]
  2768. assert len(data) == 3
  2769. assert data[0]["key_transaction"] == 0
  2770. assert data[0]["transaction"] == "/blah_transaction/"
  2771. assert data[1]["key_transaction"] == 0
  2772. assert data[1]["transaction"] == "/foo_transaction/"
  2773. assert data[2]["key_transaction"] == 0
  2774. assert data[2]["transaction"] == "/zoo_transaction/"
  2775. def test_key_transactions_orderby(self):
  2776. transactions = ["/blah_transaction/"]
  2777. key_transactions = [
  2778. "/foo_transaction/",
  2779. "/zoo_transaction/",
  2780. ]
  2781. for transaction in transactions:
  2782. self.transaction_data["transaction"] = transaction
  2783. self.store_event(self.transaction_data, self.project.id)
  2784. for transaction in key_transactions:
  2785. self.transaction_data["transaction"] = transaction
  2786. self.store_event(self.transaction_data, self.project.id)
  2787. KeyTransaction.objects.create(
  2788. owner=self.user,
  2789. organization=self.organization,
  2790. transaction=transaction,
  2791. project=self.project,
  2792. )
  2793. query = {
  2794. "project": [self.project.id],
  2795. "field": [
  2796. "key_transaction",
  2797. "transaction",
  2798. "transaction.status",
  2799. "project",
  2800. "epm()",
  2801. "failure_rate()",
  2802. "percentile(transaction.duration, 0.95)",
  2803. ],
  2804. }
  2805. # test ascending order
  2806. query["orderby"] = ["key_transaction", "transaction"]
  2807. response = self.do_request(query)
  2808. assert response.status_code == 200, response.content
  2809. data = response.data["data"]
  2810. assert len(data) == 3
  2811. assert data[0]["key_transaction"] == 0
  2812. assert data[0]["transaction"] == "/blah_transaction/"
  2813. assert data[1]["key_transaction"] == 1
  2814. assert data[1]["transaction"] == "/foo_transaction/"
  2815. assert data[2]["key_transaction"] == 1
  2816. assert data[2]["transaction"] == "/zoo_transaction/"
  2817. # test descending order
  2818. query["orderby"] = ["-key_transaction", "-transaction"]
  2819. response = self.do_request(query)
  2820. assert response.status_code == 200, response.content
  2821. data = response.data["data"]
  2822. assert len(data) == 3
  2823. assert data[0]["key_transaction"] == 1
  2824. assert data[0]["transaction"] == "/zoo_transaction/"
  2825. assert data[1]["key_transaction"] == 1
  2826. assert data[1]["transaction"] == "/foo_transaction/"
  2827. assert data[2]["key_transaction"] == 0
  2828. assert data[2]["transaction"] == "/blah_transaction/"
  2829. def test_key_transactions_query(self):
  2830. transactions = ["/blah_transaction/"]
  2831. key_transactions = [
  2832. "/foo_transaction/",
  2833. "/zoo_transaction/",
  2834. ]
  2835. for transaction in transactions:
  2836. self.transaction_data["transaction"] = transaction
  2837. self.store_event(self.transaction_data, self.project.id)
  2838. for transaction in key_transactions:
  2839. self.transaction_data["transaction"] = transaction
  2840. self.store_event(self.transaction_data, self.project.id)
  2841. KeyTransaction.objects.create(
  2842. owner=self.user,
  2843. organization=self.organization,
  2844. transaction=transaction,
  2845. project=self.project,
  2846. )
  2847. query = {
  2848. "project": [self.project.id],
  2849. "orderby": "transaction",
  2850. "field": [
  2851. "key_transaction",
  2852. "transaction",
  2853. "transaction.status",
  2854. "project",
  2855. "epm()",
  2856. "failure_rate()",
  2857. "percentile(transaction.duration, 0.95)",
  2858. ],
  2859. }
  2860. # key transactions
  2861. query["query"] = "has:key_transaction"
  2862. response = self.do_request(query)
  2863. assert response.status_code == 200, response.content
  2864. data = response.data["data"]
  2865. assert len(data) == 2
  2866. assert data[0]["key_transaction"] == 1
  2867. assert data[0]["transaction"] == "/foo_transaction/"
  2868. assert data[1]["key_transaction"] == 1
  2869. assert data[1]["transaction"] == "/zoo_transaction/"
  2870. # key transactions
  2871. query["query"] = "key_transaction:true"
  2872. response = self.do_request(query)
  2873. assert response.status_code == 200, response.content
  2874. data = response.data["data"]
  2875. assert len(data) == 2
  2876. assert data[0]["key_transaction"] == 1
  2877. assert data[0]["transaction"] == "/foo_transaction/"
  2878. assert data[1]["key_transaction"] == 1
  2879. assert data[1]["transaction"] == "/zoo_transaction/"
  2880. # not key transactions
  2881. query["query"] = "!has:key_transaction"
  2882. response = self.do_request(query)
  2883. assert response.status_code == 200, response.content
  2884. data = response.data["data"]
  2885. assert len(data) == 1
  2886. assert data[0]["key_transaction"] == 0
  2887. assert data[0]["transaction"] == "/blah_transaction/"
  2888. # not key transactions
  2889. query["query"] = "key_transaction:false"
  2890. response = self.do_request(query)
  2891. assert response.status_code == 200, response.content
  2892. data = response.data["data"]
  2893. assert len(data) == 1
  2894. assert data[0]["key_transaction"] == 0
  2895. assert data[0]["transaction"] == "/blah_transaction/"
  2896. def test_no_pagination_param(self):
  2897. self.store_event(
  2898. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  2899. project_id=self.project.id,
  2900. )
  2901. query = {"field": ["id", "project.id"], "project": [self.project.id], "noPagination": True}
  2902. response = self.do_request(query)
  2903. assert response.status_code == 200
  2904. assert len(response.data["data"]) == 1
  2905. assert "Link" not in response
  2906. def test_nan_result(self):
  2907. query = {"field": ["apdex(300)"], "project": [self.project.id], "query": f"id:{'0' * 32}"}
  2908. response = self.do_request(query)
  2909. assert response.status_code == 200
  2910. assert len(response.data["data"]) == 1
  2911. assert response.data["data"][0]["apdex_300"] == 0