test_organization_events_v2.py 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551
  1. from __future__ import absolute_import
  2. import six
  3. import random
  4. import mock
  5. from pytz import utc
  6. from datetime import timedelta
  7. from math import ceil
  8. from django.core.urlresolvers import reverse
  9. from sentry.testutils import APITestCase, SnubaTestCase
  10. from sentry.testutils.helpers import parse_link_header
  11. from sentry.testutils.helpers.datetime import before_now, iso_format
  12. from sentry.utils.samples import load_data
  13. from sentry.utils.compat.mock import patch
  14. from sentry.utils.compat import zip
  15. from sentry.utils.snuba import (
  16. RateLimitExceeded,
  17. QueryIllegalTypeOfArgument,
  18. QueryExecutionError,
  19. )
  20. class OrganizationEventsV2EndpointTest(APITestCase, SnubaTestCase):
  21. def setUp(self):
  22. super(OrganizationEventsV2EndpointTest, self).setUp()
  23. self.min_ago = iso_format(before_now(minutes=1))
  24. self.two_min_ago = iso_format(before_now(minutes=2))
  25. def do_request(self, query, features=None):
  26. if features is None:
  27. features = {"organizations:discover-basic": True}
  28. self.login_as(user=self.user)
  29. url = reverse(
  30. "sentry-api-0-organization-eventsv2",
  31. kwargs={"organization_slug": self.organization.slug},
  32. )
  33. with self.feature(features):
  34. return self.client.get(url, query, format="json")
  35. def test_no_projects(self):
  36. response = self.do_request({})
  37. assert response.status_code == 200, response.content
  38. assert len(response.data) == 0
  39. def test_performance_view_feature(self):
  40. self.store_event(
  41. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  42. project_id=self.project.id,
  43. )
  44. query = {"field": ["id", "project.id"], "project": [self.project.id]}
  45. response = self.do_request(query)
  46. assert response.status_code == 200
  47. assert len(response.data["data"]) == 1
  48. def test_multi_project_feature_gate_rejection(self):
  49. team = self.create_team(organization=self.organization, members=[self.user])
  50. project = self.create_project(organization=self.organization, teams=[team])
  51. project2 = self.create_project(organization=self.organization, teams=[team])
  52. self.store_event(
  53. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  54. project_id=project.id,
  55. )
  56. self.store_event(
  57. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  58. project_id=project2.id,
  59. )
  60. query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
  61. response = self.do_request(query)
  62. assert response.status_code == 400
  63. assert "events from multiple projects" in response.data["detail"]
  64. def test_invalid_search_terms(self):
  65. project = self.create_project()
  66. self.store_event(
  67. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  68. project_id=project.id,
  69. )
  70. query = {"field": ["id"], "query": "hi \n there"}
  71. response = self.do_request(query)
  72. assert response.status_code == 400, response.content
  73. assert (
  74. response.data["detail"]
  75. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  76. )
  77. @patch("sentry.snuba.discover.raw_query")
  78. def test_handling_snuba_errors(self, mock_query):
  79. mock_query.side_effect = RateLimitExceeded("test")
  80. project = self.create_project()
  81. self.store_event(
  82. data={"event_id": "a" * 32, "message": "how to make fast"}, project_id=project.id
  83. )
  84. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  85. response = self.do_request(query)
  86. assert response.status_code == 400, response.content
  87. assert (
  88. response.data["detail"]
  89. == "Query timeout. Please try again. If the problem persists try a smaller date range or fewer projects."
  90. )
  91. mock_query.side_effect = QueryExecutionError("test")
  92. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  93. response = self.do_request(query)
  94. assert response.status_code == 400, response.content
  95. assert response.data["detail"] == "Internal error. Your query failed to run."
  96. mock_query.side_effect = QueryIllegalTypeOfArgument("test")
  97. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  98. response = self.do_request(query)
  99. assert response.status_code == 400, response.content
  100. assert response.data["detail"] == "Invalid query. Argument to function is wrong type."
  101. def test_out_of_retention(self):
  102. self.create_project()
  103. with self.options({"system.event-retention-days": 10}):
  104. query = {
  105. "field": ["id", "timestamp"],
  106. "orderby": ["-timestamp", "-id"],
  107. "start": iso_format(before_now(days=20)),
  108. "end": iso_format(before_now(days=15)),
  109. }
  110. response = self.do_request(query)
  111. assert response.status_code == 400, response.content
  112. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  113. def test_raw_data(self):
  114. project = self.create_project()
  115. self.store_event(
  116. data={
  117. "event_id": "a" * 32,
  118. "environment": "staging",
  119. "timestamp": self.two_min_ago,
  120. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  121. },
  122. project_id=project.id,
  123. )
  124. self.store_event(
  125. data={
  126. "event_id": "b" * 32,
  127. "environment": "staging",
  128. "timestamp": self.min_ago,
  129. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  130. },
  131. project_id=project.id,
  132. )
  133. query = {
  134. "field": ["id", "project.id", "user.email", "user.ip", "timestamp"],
  135. "orderby": "-timestamp",
  136. }
  137. response = self.do_request(query)
  138. assert response.status_code == 200, response.content
  139. data = response.data["data"]
  140. assert len(data) == 2
  141. assert data[0]["id"] == "b" * 32
  142. assert data[0]["project.id"] == project.id
  143. assert data[0]["user.email"] == "foo@example.com"
  144. assert "project.name" not in data[0], "project.id does not auto select name"
  145. assert "project" not in data[0]
  146. meta = response.data["meta"]
  147. assert meta["id"] == "string"
  148. assert meta["user.email"] == "string"
  149. assert meta["user.ip"] == "string"
  150. assert meta["timestamp"] == "date"
  151. def test_project_name(self):
  152. project = self.create_project()
  153. self.store_event(
  154. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  155. project_id=project.id,
  156. )
  157. query = {"field": ["project.name", "environment"]}
  158. response = self.do_request(query)
  159. assert response.status_code == 200, response.content
  160. assert len(response.data["data"]) == 1
  161. assert response.data["data"][0]["project.name"] == project.slug
  162. assert "project.id" not in response.data["data"][0]
  163. assert response.data["data"][0]["environment"] == "staging"
  164. def test_project_without_name(self):
  165. project = self.create_project()
  166. self.store_event(
  167. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  168. project_id=project.id,
  169. )
  170. query = {"field": ["project", "environment"]}
  171. response = self.do_request(query)
  172. assert response.status_code == 200, response.content
  173. assert len(response.data["data"]) == 1
  174. assert response.data["data"][0]["project"] == project.slug
  175. assert response.data["meta"]["project"] == "string"
  176. assert "project.id" not in response.data["data"][0]
  177. assert response.data["data"][0]["environment"] == "staging"
  178. def test_project_in_query(self):
  179. project = self.create_project()
  180. self.store_event(
  181. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  182. project_id=project.id,
  183. )
  184. query = {
  185. "field": ["project", "count()"],
  186. "query": 'project:"%s"' % project.slug,
  187. "statsPeriod": "14d",
  188. }
  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 "project.id" not in response.data["data"][0]
  194. def test_project_in_query_not_in_header(self):
  195. project = self.create_project()
  196. other_project = self.create_project()
  197. self.store_event(
  198. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  199. project_id=project.id,
  200. )
  201. query = {
  202. "field": ["project", "count()"],
  203. "query": 'project:"%s"' % project.slug,
  204. "statsPeriod": "14d",
  205. "project": other_project.id,
  206. }
  207. response = self.do_request(query)
  208. assert response.status_code == 400, response.content
  209. assert (
  210. response.data["detail"]
  211. == "Invalid query. Project %s does not exist or is not an actively selected project."
  212. % project.slug
  213. )
  214. def test_project_in_query_does_not_exist(self):
  215. project = self.create_project()
  216. self.store_event(
  217. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  218. project_id=project.id,
  219. )
  220. query = {
  221. "field": ["project", "count()"],
  222. "query": "project:morty",
  223. "statsPeriod": "14d",
  224. }
  225. response = self.do_request(query)
  226. assert response.status_code == 400, response.content
  227. assert (
  228. response.data["detail"]
  229. == "Invalid query. Project morty does not exist or is not an actively selected project."
  230. )
  231. def test_not_project_in_query_but_in_header(self):
  232. team = self.create_team(organization=self.organization, members=[self.user])
  233. project = self.create_project(organization=self.organization, teams=[team])
  234. project2 = self.create_project(organization=self.organization, teams=[team])
  235. self.store_event(
  236. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  237. project_id=project.id,
  238. )
  239. self.store_event(
  240. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  241. project_id=project2.id,
  242. )
  243. query = {
  244. "field": ["id", "project.id"],
  245. "project": [project.id],
  246. "query": "!project:{}".format(project2.slug),
  247. }
  248. response = self.do_request(query)
  249. assert response.status_code == 200
  250. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  251. def test_not_project_in_query_with_all_projects(self):
  252. team = self.create_team(organization=self.organization, members=[self.user])
  253. project = self.create_project(organization=self.organization, teams=[team])
  254. project2 = self.create_project(organization=self.organization, teams=[team])
  255. self.store_event(
  256. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  257. project_id=project.id,
  258. )
  259. self.store_event(
  260. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  261. project_id=project2.id,
  262. )
  263. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  264. query = {
  265. "field": ["id", "project.id"],
  266. "project": [-1],
  267. "query": "!project:{}".format(project2.slug),
  268. }
  269. response = self.do_request(query, features=features)
  270. assert response.status_code == 200
  271. assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
  272. def test_project_condition_used_for_automatic_filters(self):
  273. project = self.create_project()
  274. self.store_event(
  275. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  276. project_id=project.id,
  277. )
  278. query = {
  279. "field": ["project", "count()"],
  280. "query": 'project:"%s"' % project.slug,
  281. "statsPeriod": "14d",
  282. }
  283. response = self.do_request(query)
  284. assert response.status_code == 200, response.content
  285. assert len(response.data["data"]) == 1
  286. assert response.data["data"][0]["project"] == project.slug
  287. assert "project.id" not in response.data["data"][0]
  288. def test_auto_insert_project_name_when_event_id_present(self):
  289. project = self.create_project()
  290. self.store_event(
  291. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  292. project_id=project.id,
  293. )
  294. query = {
  295. "field": ["id"],
  296. "statsPeriod": "1h",
  297. }
  298. response = self.do_request(query)
  299. assert response.status_code == 200, response.content
  300. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32}]
  301. def test_auto_insert_project_name_when_event_id_present_with_aggregate(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 = {
  308. "field": ["id", "count()"],
  309. "statsPeriod": "1h",
  310. }
  311. response = self.do_request(query)
  312. assert response.status_code == 200, response.content
  313. assert response.data["data"] == [{"project.name": project.slug, "id": "a" * 32, "count": 1}]
  314. def test_user_search(self):
  315. project = self.create_project()
  316. data = load_data("transaction", timestamp=before_now(minutes=1))
  317. data["user"] = {
  318. "email": "foo@example.com",
  319. "id": "123",
  320. "ip_address": "127.0.0.1",
  321. "username": "foo",
  322. }
  323. self.store_event(data, project_id=project.id)
  324. fields = {
  325. "email": "user.email",
  326. "id": "user.id",
  327. "ip_address": "user.ip",
  328. "username": "user.username",
  329. }
  330. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  331. for key, value in data["user"].items():
  332. field = fields[key]
  333. query = {
  334. "field": ["project", "user"],
  335. "query": "{}:{}".format(field, value),
  336. "statsPeriod": "14d",
  337. }
  338. response = self.do_request(query, features=features)
  339. assert response.status_code == 200, response.content
  340. assert len(response.data["data"]) == 1
  341. assert response.data["data"][0]["project"] == project.slug
  342. assert response.data["data"][0]["user"] == "id:123"
  343. def test_has_user(self):
  344. project = self.create_project()
  345. data = load_data("transaction", timestamp=before_now(minutes=1))
  346. self.store_event(data, project_id=project.id)
  347. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  348. for value in data["user"].values():
  349. query = {"field": ["project", "user"], "query": "has:user", "statsPeriod": "14d"}
  350. response = self.do_request(query, features=features)
  351. assert response.status_code == 200, response.content
  352. assert len(response.data["data"]) == 1
  353. assert response.data["data"][0]["user"] == "ip:{}".format(data["user"]["ip_address"])
  354. def test_has_issue(self):
  355. project = self.create_project()
  356. event = self.store_event(
  357. {"timestamp": iso_format(before_now(minutes=1))}, project_id=project.id
  358. )
  359. data = load_data("transaction", timestamp=before_now(minutes=1))
  360. self.store_event(data, project_id=project.id)
  361. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  362. query = {"field": ["project", "issue"], "query": "has:issue", "statsPeriod": "14d"}
  363. response = self.do_request(query, features=features)
  364. assert response.status_code == 200, response.content
  365. assert len(response.data["data"]) == 1
  366. assert response.data["data"][0]["issue"] == event.group.qualified_short_id
  367. query = {"field": ["project", "issue"], "query": "!has:issue", "statsPeriod": "14d"}
  368. response = self.do_request(query, features=features)
  369. assert response.status_code == 200, response.content
  370. assert len(response.data["data"]) == 1
  371. assert response.data["data"][0]["issue"] == "unknown"
  372. def test_negative_user_search(self):
  373. project = self.create_project()
  374. user_data = {"email": "foo@example.com", "id": "123", "username": "foo"}
  375. # Load an event with data that shouldn't match
  376. data = load_data("transaction", timestamp=before_now(minutes=1))
  377. data["transaction"] = "/transactions/nomatch"
  378. event_user = user_data.copy()
  379. event_user["id"] = "undefined"
  380. data["user"] = event_user
  381. self.store_event(data, project_id=project.id)
  382. # Load a matching event
  383. data = load_data("transaction", timestamp=before_now(minutes=1))
  384. data["transaction"] = "/transactions/matching"
  385. data["user"] = user_data
  386. self.store_event(data, project_id=project.id)
  387. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  388. query = {
  389. "field": ["project", "user"],
  390. "query": '!user:"id:undefined"',
  391. "statsPeriod": "14d",
  392. }
  393. response = self.do_request(query, features=features)
  394. assert response.status_code == 200, response.content
  395. assert len(response.data["data"]) == 1
  396. assert response.data["data"][0]["user"] == "id:{}".format(user_data["id"])
  397. assert "user.email" not in response.data["data"][0]
  398. assert "user.id" not in response.data["data"][0]
  399. def test_not_project_in_query(self):
  400. project1 = self.create_project()
  401. project2 = self.create_project()
  402. self.store_event(
  403. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  404. project_id=project1.id,
  405. )
  406. self.store_event(
  407. data={"event_id": "b" * 32, "environment": "staging", "timestamp": self.min_ago},
  408. project_id=project2.id,
  409. )
  410. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  411. query = {
  412. "field": ["project", "count()"],
  413. "query": '!project:"%s"' % project1.slug,
  414. "statsPeriod": "14d",
  415. }
  416. response = self.do_request(query, features=features)
  417. assert response.status_code == 200, response.content
  418. assert len(response.data["data"]) == 1
  419. assert response.data["data"][0]["project"] == project2.slug
  420. assert "project.id" not in response.data["data"][0]
  421. def test_error_handled_condition(self):
  422. self.login_as(user=self.user)
  423. project = self.create_project()
  424. prototype = load_data("android-ndk")
  425. events = (
  426. ("a" * 32, "not handled", False),
  427. ("b" * 32, "was handled", True),
  428. ("c" * 32, "undefined", None),
  429. )
  430. for event in events:
  431. prototype["event_id"] = event[0]
  432. prototype["message"] = event[1]
  433. prototype["exception"]["values"][0]["value"] = event[1]
  434. prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
  435. prototype["timestamp"] = self.two_min_ago
  436. self.store_event(data=prototype, project_id=project.id)
  437. with self.feature("organizations:discover-basic"):
  438. query = {
  439. "field": ["message", "error.handled"],
  440. "query": "error.handled:0",
  441. "orderby": "message",
  442. }
  443. response = self.do_request(query)
  444. assert response.status_code == 200
  445. assert 1 == len(response.data["data"])
  446. assert [0] == response.data["data"][0]["error.handled"]
  447. with self.feature("organizations:discover-basic"):
  448. query = {
  449. "field": ["message", "error.handled"],
  450. "query": "error.handled:1",
  451. "orderby": "message",
  452. }
  453. response = self.do_request(query)
  454. assert response.status_code == 200, response.data
  455. assert 2 == len(response.data["data"])
  456. assert [None] == response.data["data"][0]["error.handled"]
  457. assert [1] == response.data["data"][1]["error.handled"]
  458. def test_implicit_groupby(self):
  459. project = self.create_project()
  460. self.store_event(
  461. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  462. project_id=project.id,
  463. )
  464. event1 = self.store_event(
  465. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_1"]},
  466. project_id=project.id,
  467. )
  468. event2 = self.store_event(
  469. data={"event_id": "c" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  470. project_id=project.id,
  471. )
  472. query = {"field": ["count(id)", "project.id", "issue.id"], "orderby": "issue.id"}
  473. response = self.do_request(query)
  474. assert response.status_code == 200, response.content
  475. assert len(response.data["data"]) == 2
  476. data = response.data["data"]
  477. assert data[0] == {
  478. "project.id": project.id,
  479. "issue.id": event1.group_id,
  480. "count_id": 2,
  481. }
  482. assert data[1] == {
  483. "project.id": project.id,
  484. "issue.id": event2.group_id,
  485. "count_id": 1,
  486. }
  487. meta = response.data["meta"]
  488. assert meta["count_id"] == "integer"
  489. def test_orderby(self):
  490. project = self.create_project()
  491. self.store_event(
  492. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  493. )
  494. self.store_event(
  495. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project.id
  496. )
  497. self.store_event(
  498. data={"event_id": "c" * 32, "timestamp": self.min_ago}, project_id=project.id
  499. )
  500. query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
  501. response = self.do_request(query)
  502. assert response.status_code == 200, response.content
  503. data = response.data["data"]
  504. assert data[0]["id"] == "c" * 32
  505. assert data[1]["id"] == "b" * 32
  506. assert data[2]["id"] == "a" * 32
  507. def test_sort_title(self):
  508. project = self.create_project()
  509. self.store_event(
  510. data={"event_id": "a" * 32, "message": "zlast", "timestamp": self.two_min_ago},
  511. project_id=project.id,
  512. )
  513. self.store_event(
  514. data={"event_id": "b" * 32, "message": "second", "timestamp": self.min_ago},
  515. project_id=project.id,
  516. )
  517. self.store_event(
  518. data={"event_id": "c" * 32, "message": "first", "timestamp": self.min_ago},
  519. project_id=project.id,
  520. )
  521. query = {"field": ["id", "title"], "sort": "title"}
  522. response = self.do_request(query)
  523. assert response.status_code == 200, response.content
  524. data = response.data["data"]
  525. assert data[0]["id"] == "c" * 32
  526. assert data[1]["id"] == "b" * 32
  527. assert data[2]["id"] == "a" * 32
  528. def test_sort_invalid(self):
  529. project = self.create_project()
  530. self.store_event(
  531. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  532. )
  533. query = {"field": ["id"], "sort": "garbage"}
  534. response = self.do_request(query)
  535. assert response.status_code == 400
  536. assert "order by" in response.content
  537. def test_latest_release_alias(self):
  538. project = self.create_project()
  539. event1 = self.store_event(
  540. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "release": "0.8"},
  541. project_id=project.id,
  542. )
  543. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  544. response = self.do_request(query)
  545. assert response.status_code == 200, response.content
  546. data = response.data["data"]
  547. assert data[0]["issue.id"] == event1.group_id
  548. assert data[0]["release"] == "0.8"
  549. event2 = self.store_event(
  550. data={"event_id": "a" * 32, "timestamp": self.min_ago, "release": "0.9"},
  551. project_id=project.id,
  552. )
  553. query = {"field": ["issue.id", "release"], "query": "release:latest"}
  554. response = self.do_request(query)
  555. assert response.status_code == 200, response.content
  556. data = response.data["data"]
  557. assert data[0]["issue.id"] == event2.group_id
  558. assert data[0]["release"] == "0.9"
  559. def test_aliased_fields(self):
  560. project = self.create_project()
  561. event1 = self.store_event(
  562. data={
  563. "event_id": "a" * 32,
  564. "timestamp": self.min_ago,
  565. "fingerprint": ["group_1"],
  566. "user": {"email": "foo@example.com"},
  567. },
  568. project_id=project.id,
  569. )
  570. event2 = self.store_event(
  571. data={
  572. "event_id": "b" * 32,
  573. "timestamp": self.min_ago,
  574. "fingerprint": ["group_2"],
  575. "user": {"email": "foo@example.com"},
  576. },
  577. project_id=project.id,
  578. )
  579. self.store_event(
  580. data={
  581. "event_id": "c" * 32,
  582. "timestamp": self.min_ago,
  583. "fingerprint": ["group_2"],
  584. "user": {"email": "bar@example.com"},
  585. },
  586. project_id=project.id,
  587. )
  588. query = {
  589. "field": ["issue.id", "count(id)", "count_unique(user)"],
  590. "orderby": "issue.id",
  591. }
  592. response = self.do_request(query)
  593. assert response.status_code == 200, response.content
  594. assert len(response.data["data"]) == 2
  595. data = response.data["data"]
  596. assert data[0]["issue.id"] == event1.group_id
  597. assert data[0]["count_id"] == 1
  598. assert data[0]["count_unique_user"] == 1
  599. assert "projectid" not in data[0]
  600. assert "project.id" not in data[0]
  601. assert data[1]["issue.id"] == event2.group_id
  602. assert data[1]["count_id"] == 2
  603. assert data[1]["count_unique_user"] == 2
  604. def test_aggregate_field_with_dotted_param(self):
  605. project = self.create_project()
  606. event1 = self.store_event(
  607. data={
  608. "event_id": "a" * 32,
  609. "timestamp": self.min_ago,
  610. "fingerprint": ["group_1"],
  611. "user": {"id": "123", "email": "foo@example.com"},
  612. },
  613. project_id=project.id,
  614. )
  615. event2 = self.store_event(
  616. data={
  617. "event_id": "b" * 32,
  618. "timestamp": self.min_ago,
  619. "fingerprint": ["group_2"],
  620. "user": {"id": "123", "email": "foo@example.com"},
  621. },
  622. project_id=project.id,
  623. )
  624. self.store_event(
  625. data={
  626. "event_id": "c" * 32,
  627. "timestamp": self.min_ago,
  628. "fingerprint": ["group_2"],
  629. "user": {"id": "456", "email": "bar@example.com"},
  630. },
  631. project_id=project.id,
  632. )
  633. query = {
  634. "field": ["issue.id", "issue_title", "count(id)", "count_unique(user.email)"],
  635. "orderby": "issue.id",
  636. }
  637. response = self.do_request(query)
  638. assert response.status_code == 200, response.content
  639. assert len(response.data["data"]) == 2
  640. data = response.data["data"]
  641. assert data[0]["issue.id"] == event1.group_id
  642. assert data[0]["count_id"] == 1
  643. assert data[0]["count_unique_user_email"] == 1
  644. assert "projectid" not in data[0]
  645. assert "project.id" not in data[0]
  646. assert data[1]["issue.id"] == event2.group_id
  647. assert data[1]["count_id"] == 2
  648. assert data[1]["count_unique_user_email"] == 2
  649. def test_failure_rate_alias_field(self):
  650. project = self.create_project()
  651. data = load_data("transaction", timestamp=before_now(minutes=1))
  652. data["transaction"] = "/failure_rate/success"
  653. self.store_event(data, project_id=project.id)
  654. data = load_data("transaction", timestamp=before_now(minutes=1))
  655. data["transaction"] = "/failure_rate/unknown"
  656. data["contexts"]["trace"]["status"] = "unknown_error"
  657. self.store_event(data, project_id=project.id)
  658. for i in range(6):
  659. data = load_data("transaction", timestamp=before_now(minutes=1))
  660. data["transaction"] = "/failure_rate/{}".format(i)
  661. data["contexts"]["trace"]["status"] = "unauthenticated"
  662. self.store_event(data, project_id=project.id)
  663. query = {"field": ["failure_rate()"], "query": "event.type:transaction"}
  664. response = self.do_request(query)
  665. assert response.status_code == 200, response.content
  666. assert len(response.data["data"]) == 1
  667. data = response.data["data"]
  668. assert data[0]["failure_rate"] == 0.75
  669. def test_user_misery_alias_field(self):
  670. project = self.create_project()
  671. events = [
  672. ("one", 300),
  673. ("one", 300),
  674. ("two", 3000),
  675. ("two", 3000),
  676. ("three", 300),
  677. ("three", 3000),
  678. ]
  679. for idx, event in enumerate(events):
  680. data = load_data(
  681. "transaction",
  682. timestamp=before_now(minutes=(1 + idx)),
  683. start_timestamp=before_now(minutes=(1 + idx), milliseconds=event[1]),
  684. )
  685. data["event_id"] = "{}".format(idx) * 32
  686. data["transaction"] = "/user_misery/horribilis/{}".format(idx)
  687. data["user"] = {"email": "{}@example.com".format(event[0])}
  688. self.store_event(data, project_id=project.id)
  689. query = {"field": ["user_misery(300)"], "query": "event.type:transaction"}
  690. response = self.do_request(query)
  691. assert response.status_code == 200, response.content
  692. assert len(response.data["data"]) == 1
  693. data = response.data["data"]
  694. assert data[0]["user_misery_300"] == 2
  695. def test_aggregation(self):
  696. project = self.create_project()
  697. self.store_event(
  698. data={
  699. "event_id": "a" * 32,
  700. "timestamp": self.min_ago,
  701. "fingerprint": ["group_1"],
  702. "user": {"email": "foo@example.com"},
  703. "environment": "prod",
  704. "tags": {"sub_customer.is-Enterprise-42": "1"},
  705. },
  706. project_id=project.id,
  707. )
  708. self.store_event(
  709. data={
  710. "event_id": "b" * 32,
  711. "timestamp": self.min_ago,
  712. "fingerprint": ["group_2"],
  713. "user": {"email": "foo@example.com"},
  714. "environment": "staging",
  715. "tags": {"sub_customer.is-Enterprise-42": "1"},
  716. },
  717. project_id=project.id,
  718. )
  719. self.store_event(
  720. data={
  721. "event_id": "c" * 32,
  722. "timestamp": self.min_ago,
  723. "fingerprint": ["group_2"],
  724. "user": {"email": "foo@example.com"},
  725. "environment": "prod",
  726. "tags": {"sub_customer.is-Enterprise-42": "0"},
  727. },
  728. project_id=project.id,
  729. )
  730. self.store_event(
  731. data={
  732. "event_id": "d" * 32,
  733. "timestamp": self.min_ago,
  734. "fingerprint": ["group_2"],
  735. "user": {"email": "foo@example.com"},
  736. "environment": "prod",
  737. "tags": {"sub_customer.is-Enterprise-42": "1"},
  738. },
  739. project_id=project.id,
  740. )
  741. query = {
  742. "field": ["sub_customer.is-Enterprise-42", "count(sub_customer.is-Enterprise-42)"],
  743. "orderby": "sub_customer.is-Enterprise-42",
  744. }
  745. response = self.do_request(query)
  746. assert response.status_code == 200, response.content
  747. assert len(response.data["data"]) == 2
  748. data = response.data["data"]
  749. assert data[0]["count_sub_customer_is-Enterprise-42"] == 1
  750. assert data[1]["count_sub_customer_is-Enterprise-42"] == 3
  751. def test_aggregation_comparison(self):
  752. project = self.create_project()
  753. self.store_event(
  754. data={
  755. "event_id": "a" * 32,
  756. "timestamp": self.min_ago,
  757. "fingerprint": ["group_1"],
  758. "user": {"email": "foo@example.com"},
  759. },
  760. project_id=project.id,
  761. )
  762. event = self.store_event(
  763. data={
  764. "event_id": "b" * 32,
  765. "timestamp": self.min_ago,
  766. "fingerprint": ["group_2"],
  767. "user": {"email": "foo@example.com"},
  768. },
  769. project_id=project.id,
  770. )
  771. self.store_event(
  772. data={
  773. "event_id": "c" * 32,
  774. "timestamp": self.min_ago,
  775. "fingerprint": ["group_2"],
  776. "user": {"email": "bar@example.com"},
  777. },
  778. project_id=project.id,
  779. )
  780. self.store_event(
  781. data={
  782. "event_id": "d" * 32,
  783. "timestamp": self.min_ago,
  784. "fingerprint": ["group_3"],
  785. "user": {"email": "bar@example.com"},
  786. },
  787. project_id=project.id,
  788. )
  789. self.store_event(
  790. data={
  791. "event_id": "e" * 32,
  792. "timestamp": self.min_ago,
  793. "fingerprint": ["group_3"],
  794. "user": {"email": "bar@example.com"},
  795. },
  796. project_id=project.id,
  797. )
  798. query = {
  799. "field": ["issue.id", "count(id)", "count_unique(user)"],
  800. "query": "count(id):>1 count_unique(user):>1",
  801. "orderby": "issue.id",
  802. }
  803. response = self.do_request(query)
  804. assert response.status_code == 200, response.content
  805. assert len(response.data["data"]) == 1
  806. data = response.data["data"]
  807. assert data[0]["issue.id"] == event.group_id
  808. assert data[0]["count_id"] == 2
  809. assert data[0]["count_unique_user"] == 2
  810. def test_aggregation_alias_comparison(self):
  811. project = self.create_project()
  812. data = load_data(
  813. "transaction",
  814. timestamp=before_now(minutes=1),
  815. start_timestamp=before_now(minutes=1, seconds=5),
  816. )
  817. data["transaction"] = "/aggregates/1"
  818. self.store_event(data, project_id=project.id)
  819. data = load_data(
  820. "transaction",
  821. timestamp=before_now(minutes=1),
  822. start_timestamp=before_now(minutes=1, seconds=3),
  823. )
  824. data["transaction"] = "/aggregates/2"
  825. event = self.store_event(data, project_id=project.id)
  826. query = {
  827. "field": ["transaction", "p95()"],
  828. "query": "event.type:transaction p95():<4000",
  829. "orderby": ["transaction"],
  830. }
  831. response = self.do_request(query)
  832. assert response.status_code == 200, response.content
  833. assert len(response.data["data"]) == 1
  834. data = response.data["data"]
  835. assert data[0]["transaction"] == event.transaction
  836. assert data[0]["p95"] == 3000
  837. def test_aggregation_comparison_with_conditions(self):
  838. project = self.create_project()
  839. self.store_event(
  840. data={
  841. "event_id": "a" * 32,
  842. "timestamp": self.min_ago,
  843. "fingerprint": ["group_1"],
  844. "user": {"email": "foo@example.com"},
  845. "environment": "prod",
  846. },
  847. project_id=project.id,
  848. )
  849. self.store_event(
  850. data={
  851. "event_id": "b" * 32,
  852. "timestamp": self.min_ago,
  853. "fingerprint": ["group_2"],
  854. "user": {"email": "foo@example.com"},
  855. "environment": "staging",
  856. },
  857. project_id=project.id,
  858. )
  859. event = self.store_event(
  860. data={
  861. "event_id": "c" * 32,
  862. "timestamp": self.min_ago,
  863. "fingerprint": ["group_2"],
  864. "user": {"email": "foo@example.com"},
  865. "environment": "prod",
  866. },
  867. project_id=project.id,
  868. )
  869. self.store_event(
  870. data={
  871. "event_id": "d" * 32,
  872. "timestamp": self.min_ago,
  873. "fingerprint": ["group_2"],
  874. "user": {"email": "foo@example.com"},
  875. "environment": "prod",
  876. },
  877. project_id=project.id,
  878. )
  879. query = {
  880. "field": ["issue.id", "count(id)"],
  881. "query": "count(id):>1 user.email:foo@example.com environment:prod",
  882. "orderby": "issue.id",
  883. }
  884. response = self.do_request(query)
  885. assert response.status_code == 200, response.content
  886. assert len(response.data["data"]) == 1
  887. data = response.data["data"]
  888. assert data[0]["issue.id"] == event.group_id
  889. assert data[0]["count_id"] == 2
  890. def test_aggregation_date_comparison_with_conditions(self):
  891. project = self.create_project()
  892. event = self.store_event(
  893. data={
  894. "event_id": "a" * 32,
  895. "timestamp": self.min_ago,
  896. "fingerprint": ["group_1"],
  897. "user": {"email": "foo@example.com"},
  898. "environment": "prod",
  899. },
  900. project_id=project.id,
  901. )
  902. self.store_event(
  903. data={
  904. "event_id": "b" * 32,
  905. "timestamp": self.min_ago,
  906. "fingerprint": ["group_2"],
  907. "user": {"email": "foo@example.com"},
  908. "environment": "staging",
  909. },
  910. project_id=project.id,
  911. )
  912. self.store_event(
  913. data={
  914. "event_id": "c" * 32,
  915. "timestamp": self.min_ago,
  916. "fingerprint": ["group_2"],
  917. "user": {"email": "foo@example.com"},
  918. "environment": "prod",
  919. },
  920. project_id=project.id,
  921. )
  922. self.store_event(
  923. data={
  924. "event_id": "d" * 32,
  925. "timestamp": self.min_ago,
  926. "fingerprint": ["group_2"],
  927. "user": {"email": "foo@example.com"},
  928. "environment": "prod",
  929. },
  930. project_id=project.id,
  931. )
  932. query = {
  933. "field": ["issue.id", "max(timestamp)"],
  934. "query": "max(timestamp):>1 user.email:foo@example.com environment:prod",
  935. "orderby": "issue.id",
  936. }
  937. response = self.do_request(query)
  938. assert response.status_code == 200, response.content
  939. assert len(response.data["data"]) == 2
  940. response.data["meta"]["max_timestamp"] == "date"
  941. data = response.data["data"]
  942. assert data[0]["issue.id"] == event.group_id
  943. def test_percentile_function(self):
  944. project = self.create_project()
  945. data = load_data(
  946. "transaction",
  947. timestamp=before_now(minutes=1),
  948. start_timestamp=before_now(minutes=1, seconds=5),
  949. )
  950. data["transaction"] = "/aggregates/1"
  951. event1 = self.store_event(data, project_id=project.id)
  952. data = load_data(
  953. "transaction",
  954. timestamp=before_now(minutes=1),
  955. start_timestamp=before_now(minutes=1, seconds=3),
  956. )
  957. data["transaction"] = "/aggregates/2"
  958. event2 = self.store_event(data, project_id=project.id)
  959. query = {
  960. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  961. "query": "event.type:transaction",
  962. "orderby": ["transaction"],
  963. }
  964. response = self.do_request(query)
  965. assert response.status_code == 200, response.content
  966. assert len(response.data["data"]) == 2
  967. data = response.data["data"]
  968. assert data[0]["transaction"] == event1.transaction
  969. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  970. assert data[1]["transaction"] == event2.transaction
  971. assert data[1]["percentile_transaction_duration_0_95"] == 3000
  972. def test_percentile_function_as_condition(self):
  973. project = self.create_project()
  974. data = load_data(
  975. "transaction",
  976. timestamp=before_now(minutes=1),
  977. start_timestamp=before_now(minutes=1, seconds=5),
  978. )
  979. data["transaction"] = "/aggregates/1"
  980. event1 = self.store_event(data, project_id=project.id)
  981. data = load_data(
  982. "transaction",
  983. timestamp=before_now(minutes=1),
  984. start_timestamp=before_now(minutes=1, seconds=3),
  985. )
  986. data["transaction"] = "/aggregates/2"
  987. self.store_event(data, project_id=project.id)
  988. query = {
  989. "field": ["transaction", "percentile(transaction.duration, 0.95)"],
  990. "query": "event.type:transaction percentile(transaction.duration, 0.95):>4000",
  991. "orderby": ["transaction"],
  992. }
  993. response = self.do_request(query)
  994. assert response.status_code == 200, response.content
  995. assert len(response.data["data"]) == 1
  996. data = response.data["data"]
  997. assert data[0]["transaction"] == event1.transaction
  998. assert data[0]["percentile_transaction_duration_0_95"] == 5000
  999. def test_epm_function(self):
  1000. project = self.create_project()
  1001. data = load_data(
  1002. "transaction",
  1003. timestamp=before_now(minutes=1),
  1004. start_timestamp=before_now(minutes=1, seconds=5),
  1005. )
  1006. data["transaction"] = "/aggregates/1"
  1007. event1 = self.store_event(data, project_id=project.id)
  1008. data = load_data(
  1009. "transaction",
  1010. timestamp=before_now(minutes=1),
  1011. start_timestamp=before_now(minutes=1, seconds=3),
  1012. )
  1013. data["transaction"] = "/aggregates/2"
  1014. event2 = self.store_event(data, project_id=project.id)
  1015. query = {
  1016. "field": ["transaction", "epm()"],
  1017. "query": "event.type:transaction",
  1018. "orderby": ["transaction"],
  1019. "statsPeriod": "2m",
  1020. }
  1021. response = self.do_request(query)
  1022. assert response.status_code == 200, response.content
  1023. assert len(response.data["data"]) == 2
  1024. data = response.data["data"]
  1025. assert data[0]["transaction"] == event1.transaction
  1026. assert data[0]["epm"] == 0.5
  1027. assert data[1]["transaction"] == event2.transaction
  1028. assert data[1]["epm"] == 0.5
  1029. def test_nonexistent_fields(self):
  1030. project = self.create_project()
  1031. self.store_event(
  1032. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1033. project_id=project.id,
  1034. )
  1035. query = {"field": ["issue_world.id"]}
  1036. response = self.do_request(query)
  1037. assert response.status_code == 200, response.content
  1038. assert response.data["data"][0]["issue_world.id"] == ""
  1039. def test_no_requested_fields_or_grouping(self):
  1040. project = self.create_project()
  1041. self.store_event(
  1042. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  1043. project_id=project.id,
  1044. )
  1045. query = {"query": "test"}
  1046. response = self.do_request(query)
  1047. assert response.status_code == 400, response.content
  1048. assert response.data["detail"] == "No columns selected"
  1049. def test_condition_on_aggregate_misses(self):
  1050. project = self.create_project()
  1051. self.store_event(
  1052. data={
  1053. "event_id": "c" * 32,
  1054. "timestamp": self.min_ago,
  1055. "fingerprint": ["group_2"],
  1056. "user": {"email": "bar@example.com"},
  1057. },
  1058. project_id=project.id,
  1059. )
  1060. query = {"field": ["issue.id"], "query": "event_count:>0", "orderby": "issue.id"}
  1061. response = self.do_request(query)
  1062. assert response.status_code == 200, response.content
  1063. assert len(response.data["data"]) == 0
  1064. def test_next_prev_link_headers(self):
  1065. project = self.create_project()
  1066. events = [("a", "group_1"), ("b", "group_2"), ("c", "group_2"), ("d", "group_2")]
  1067. for e in events:
  1068. self.store_event(
  1069. data={
  1070. "event_id": e[0] * 32,
  1071. "timestamp": self.min_ago,
  1072. "fingerprint": [e[1]],
  1073. "user": {"email": "foo@example.com"},
  1074. "tags": {"language": "C++"},
  1075. },
  1076. project_id=project.id,
  1077. )
  1078. query = {
  1079. "field": ["count(id)", "issue.id", "context.key"],
  1080. "sort": "-count_id",
  1081. "query": "language:C++",
  1082. }
  1083. response = self.do_request(query)
  1084. assert response.status_code == 200, response.content
  1085. links = parse_link_header(response["Link"])
  1086. for link in links:
  1087. assert "field=issue.id" in link
  1088. assert "field=count%28id%29" in link
  1089. assert "field=context.key" in link
  1090. assert "sort=-count_id" in link
  1091. assert "query=language%3AC%2B%2B" in link
  1092. assert len(response.data["data"]) == 2
  1093. data = response.data["data"]
  1094. assert data[0]["count_id"] == 3
  1095. assert data[1]["count_id"] == 1
  1096. def test_empty_count_query(self):
  1097. project = self.create_project()
  1098. event = self.store_event(
  1099. data={
  1100. "event_id": "a" * 32,
  1101. "timestamp": iso_format(before_now(minutes=5)),
  1102. "fingerprint": ["1123581321"],
  1103. "user": {"email": "foo@example.com"},
  1104. "tags": {"language": "C++"},
  1105. },
  1106. project_id=project.id,
  1107. )
  1108. query = {
  1109. "field": ["count()"],
  1110. "query": "issue.id:%d timestamp:>%s" % (event.group_id, self.min_ago),
  1111. "statsPeriod": "14d",
  1112. }
  1113. response = self.do_request(query)
  1114. assert response.status_code == 200, response.content
  1115. data = response.data["data"]
  1116. assert len(data) == 1
  1117. assert data[0]["count"] == 0
  1118. def test_reference_event(self):
  1119. project = self.create_project()
  1120. reference = self.store_event(
  1121. data={
  1122. "event_id": "a" * 32,
  1123. "transaction": "/example",
  1124. "message": "how to make fast",
  1125. "timestamp": self.two_min_ago,
  1126. },
  1127. project_id=project.id,
  1128. )
  1129. self.store_event(
  1130. data={
  1131. "event_id": "b" * 32,
  1132. "transaction": "/example",
  1133. "message": "how to make more faster?",
  1134. "timestamp": self.min_ago,
  1135. },
  1136. project_id=project.id,
  1137. )
  1138. self.store_event(
  1139. data={
  1140. "event_id": "c" * 32,
  1141. "transaction": "/nomatch",
  1142. "message": "how to make fast",
  1143. "timestamp": self.min_ago,
  1144. },
  1145. project_id=project.id,
  1146. )
  1147. query = {
  1148. "field": ["transaction", "count()"],
  1149. "query": "",
  1150. "referenceEvent": "{}:{}".format(project.slug, reference.event_id),
  1151. }
  1152. response = self.do_request(query)
  1153. assert response.status_code == 200, response.content
  1154. assert len(response.data["data"]) == 1
  1155. data = response.data["data"]
  1156. assert data[0]["transaction"] == "/example"
  1157. def test_stack_wildcard_condition(self):
  1158. project = self.create_project()
  1159. data = load_data("javascript")
  1160. data["timestamp"] = self.min_ago
  1161. self.store_event(data=data, project_id=project.id)
  1162. query = {"field": ["stack.filename", "message"], "query": "stack.filename:*.js"}
  1163. response = self.do_request(query)
  1164. assert response.status_code == 200, response.content
  1165. assert len(response.data["data"]) == 1
  1166. assert response.data["meta"]["message"] == "string"
  1167. def test_email_wildcard_condition(self):
  1168. project = self.create_project()
  1169. data = load_data("javascript")
  1170. data["timestamp"] = self.min_ago
  1171. self.store_event(data=data, project_id=project.id)
  1172. query = {"field": ["stack.filename", "message"], "query": "user.email:*@example.org"}
  1173. response = self.do_request(query)
  1174. assert response.status_code == 200, response.content
  1175. assert len(response.data["data"]) == 1
  1176. assert response.data["meta"]["message"] == "string"
  1177. def test_transaction_event_type(self):
  1178. project = self.create_project()
  1179. data = load_data(
  1180. "transaction",
  1181. timestamp=before_now(minutes=1),
  1182. start_timestamp=before_now(minutes=1, seconds=5),
  1183. )
  1184. self.store_event(data=data, project_id=project.id)
  1185. query = {
  1186. "field": ["transaction", "transaction.duration", "transaction.status"],
  1187. "query": "event.type:transaction",
  1188. }
  1189. response = self.do_request(query)
  1190. assert response.status_code == 200, response.content
  1191. assert len(response.data["data"]) == 1
  1192. assert response.data["meta"]["transaction.duration"] == "duration"
  1193. assert response.data["meta"]["transaction.status"] == "string"
  1194. assert response.data["data"][0]["transaction.status"] == "ok"
  1195. def test_trace_columns(self):
  1196. project = self.create_project()
  1197. data = load_data(
  1198. "transaction",
  1199. timestamp=before_now(minutes=1),
  1200. start_timestamp=before_now(minutes=1, seconds=5),
  1201. )
  1202. self.store_event(data=data, project_id=project.id)
  1203. query = {"field": ["trace"], "query": "event.type:transaction"}
  1204. response = self.do_request(query)
  1205. assert response.status_code == 200, response.content
  1206. assert len(response.data["data"]) == 1
  1207. assert response.data["meta"]["trace"] == "string"
  1208. assert response.data["data"][0]["trace"] == data["contexts"]["trace"]["trace_id"]
  1209. def test_issue_in_columns(self):
  1210. project1 = self.create_project()
  1211. project2 = self.create_project()
  1212. event1 = self.store_event(
  1213. data={
  1214. "event_id": "a" * 32,
  1215. "transaction": "/example",
  1216. "message": "how to make fast",
  1217. "timestamp": self.two_min_ago,
  1218. "fingerprint": ["group_1"],
  1219. },
  1220. project_id=project1.id,
  1221. )
  1222. event2 = self.store_event(
  1223. data={
  1224. "event_id": "b" * 32,
  1225. "transaction": "/example",
  1226. "message": "how to make fast",
  1227. "timestamp": self.two_min_ago,
  1228. "fingerprint": ["group_1"],
  1229. },
  1230. project_id=project2.id,
  1231. )
  1232. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1233. query = {"field": ["id", "issue"], "orderby": ["id"]}
  1234. response = self.do_request(query, features=features)
  1235. assert response.status_code == 200, response.content
  1236. data = response.data["data"]
  1237. assert len(data) == 2
  1238. assert data[0]["id"] == event1.event_id
  1239. assert data[0]["issue.id"] == event1.group_id
  1240. assert data[0]["issue"] == event1.group.qualified_short_id
  1241. assert data[1]["id"] == event2.event_id
  1242. assert data[1]["issue.id"] == event2.group_id
  1243. assert data[1]["issue"] == event2.group.qualified_short_id
  1244. def test_issue_in_search_and_columns(self):
  1245. project1 = self.create_project()
  1246. project2 = self.create_project()
  1247. event1 = self.store_event(
  1248. data={
  1249. "event_id": "a" * 32,
  1250. "transaction": "/example",
  1251. "message": "how to make fast",
  1252. "timestamp": self.two_min_ago,
  1253. "fingerprint": ["group_1"],
  1254. },
  1255. project_id=project1.id,
  1256. )
  1257. self.store_event(
  1258. data={
  1259. "event_id": "b" * 32,
  1260. "transaction": "/example",
  1261. "message": "how to make fast",
  1262. "timestamp": self.two_min_ago,
  1263. "fingerprint": ["group_1"],
  1264. },
  1265. project_id=project2.id,
  1266. )
  1267. tests = [
  1268. ("issue", "issue:%s" % event1.group.qualified_short_id),
  1269. ("issue.id", "issue:%s" % event1.group.qualified_short_id),
  1270. ("issue", "issue.id:%s" % event1.group_id),
  1271. ("issue.id", "issue.id:%s" % event1.group_id),
  1272. ]
  1273. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1274. for testdata in tests:
  1275. query = {"field": [testdata[0]], "query": testdata[1]}
  1276. response = self.do_request(query, features=features)
  1277. assert response.status_code == 200, response.content
  1278. data = response.data["data"]
  1279. assert len(data) == 1
  1280. assert data[0]["id"] == event1.event_id
  1281. assert data[0]["issue.id"] == event1.group_id
  1282. if testdata[0] == "issue":
  1283. assert data[0]["issue"] == event1.group.qualified_short_id
  1284. else:
  1285. assert data[0].get("issue", None) is None
  1286. def test_issue_negation(self):
  1287. project1 = self.create_project()
  1288. project2 = self.create_project()
  1289. event1 = self.store_event(
  1290. data={
  1291. "event_id": "a" * 32,
  1292. "transaction": "/example",
  1293. "message": "how to make fast",
  1294. "timestamp": self.two_min_ago,
  1295. "fingerprint": ["group_1"],
  1296. },
  1297. project_id=project1.id,
  1298. )
  1299. event2 = self.store_event(
  1300. data={
  1301. "event_id": "b" * 32,
  1302. "transaction": "/example",
  1303. "message": "go really fast plz",
  1304. "timestamp": self.two_min_ago,
  1305. "fingerprint": ["group_2"],
  1306. },
  1307. project_id=project2.id,
  1308. )
  1309. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1310. query = {
  1311. "field": ["title", "issue.id"],
  1312. "query": "!issue:{}".format(event1.group.qualified_short_id),
  1313. }
  1314. response = self.do_request(query, features=features)
  1315. assert response.status_code == 200, response.content
  1316. data = response.data["data"]
  1317. assert len(data) == 1
  1318. assert data[0]["title"] == event2.title
  1319. assert data[0]["issue.id"] == event2.group_id
  1320. def test_search_for_nonexistent_issue(self):
  1321. project1 = self.create_project()
  1322. self.store_event(
  1323. data={
  1324. "event_id": "a" * 32,
  1325. "transaction": "/example",
  1326. "message": "how to make fast",
  1327. "timestamp": self.two_min_ago,
  1328. "fingerprint": ["group_1"],
  1329. },
  1330. project_id=project1.id,
  1331. )
  1332. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1333. query = {"field": ["count()"], "query": "issue.id:112358"}
  1334. response = self.do_request(query, features=features)
  1335. assert response.status_code == 200, response.content
  1336. data = response.data["data"]
  1337. assert len(data) == 1
  1338. assert data[0]["count"] == 0
  1339. def test_issue_alias_inside_aggregate(self):
  1340. project1 = self.create_project()
  1341. self.store_event(
  1342. data={
  1343. "event_id": "a" * 32,
  1344. "transaction": "/example",
  1345. "message": "how to make fast",
  1346. "timestamp": self.two_min_ago,
  1347. "fingerprint": ["group_1"],
  1348. },
  1349. project_id=project1.id,
  1350. )
  1351. self.store_event(
  1352. data={
  1353. "event_id": "b" * 32,
  1354. "transaction": "/example",
  1355. "message": "how to make fast",
  1356. "timestamp": self.two_min_ago,
  1357. "fingerprint": ["group_2"],
  1358. },
  1359. project_id=project1.id,
  1360. )
  1361. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1362. query = {
  1363. "field": ["project", "count(id)", "count_unique(issue.id)", "count_unique(issue)"],
  1364. "sort": "-count(id)",
  1365. "statsPeriod": "24h",
  1366. }
  1367. response = self.do_request(query, features=features)
  1368. assert response.status_code == 200, response.content
  1369. data = response.data["data"]
  1370. assert len(data) == 1
  1371. assert data[0]["count_id"] == 2
  1372. assert data[0]["count_unique_issue_id"] == 2
  1373. assert data[0]["count_unique_issue"] == 2
  1374. def test_project_alias_inside_aggregate(self):
  1375. project1 = self.create_project()
  1376. project2 = self.create_project()
  1377. self.store_event(
  1378. data={
  1379. "event_id": "a" * 32,
  1380. "transaction": "/example",
  1381. "message": "how to make fast",
  1382. "timestamp": self.two_min_ago,
  1383. "fingerprint": ["group_1"],
  1384. },
  1385. project_id=project1.id,
  1386. )
  1387. self.store_event(
  1388. data={
  1389. "event_id": "b" * 32,
  1390. "transaction": "/example",
  1391. "message": "how to make fast",
  1392. "timestamp": self.two_min_ago,
  1393. "fingerprint": ["group_2"],
  1394. },
  1395. project_id=project2.id,
  1396. )
  1397. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1398. query = {
  1399. "field": [
  1400. "event.type",
  1401. "count(id)",
  1402. "count_unique(project.id)",
  1403. "count_unique(project)",
  1404. ],
  1405. "sort": "-count(id)",
  1406. "statsPeriod": "24h",
  1407. }
  1408. response = self.do_request(query, features=features)
  1409. assert response.status_code == 200, response.content
  1410. data = response.data["data"]
  1411. assert len(data) == 1
  1412. assert data[0]["count_id"] == 2
  1413. assert data[0]["count_unique_project_id"] == 2
  1414. assert data[0]["count_unique_project"] == 2
  1415. def test_user_display(self):
  1416. project1 = self.create_project()
  1417. project2 = self.create_project()
  1418. self.store_event(
  1419. data={
  1420. "event_id": "a" * 32,
  1421. "transaction": "/example",
  1422. "message": "how to make fast",
  1423. "timestamp": self.two_min_ago,
  1424. "user": {"email": "cathy@example.com"},
  1425. },
  1426. project_id=project1.id,
  1427. )
  1428. self.store_event(
  1429. data={
  1430. "event_id": "b" * 32,
  1431. "transaction": "/example",
  1432. "message": "how to make fast",
  1433. "timestamp": self.two_min_ago,
  1434. "user": {"username": "catherine"},
  1435. },
  1436. project_id=project2.id,
  1437. )
  1438. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1439. query = {
  1440. "field": ["event.type", "user.display"],
  1441. "query": "user.display:cath*",
  1442. "statsPeriod": "24h",
  1443. }
  1444. response = self.do_request(query, features=features)
  1445. assert response.status_code == 200, response.content
  1446. data = response.data["data"]
  1447. assert len(data) == 2
  1448. result = set([r["user.display"] for r in data])
  1449. assert result == set(["catherine", "cathy@example.com"])
  1450. def test_user_display_with_aggregates(self):
  1451. self.login_as(user=self.user)
  1452. project1 = 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. "user": {"email": "cathy@example.com"},
  1460. },
  1461. project_id=project1.id,
  1462. )
  1463. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1464. query = {
  1465. "field": ["event.type", "user.display", "count_unique(title)"],
  1466. "statsPeriod": "24h",
  1467. }
  1468. response = self.do_request(query, features=features)
  1469. assert response.status_code == 200, response.content
  1470. data = response.data["data"]
  1471. assert len(data) == 1
  1472. result = set([r["user.display"] for r in data])
  1473. assert result == set(["cathy@example.com"])
  1474. query = {"field": ["event.type", "count_unique(user.display)"], "statsPeriod": "24h"}
  1475. response = self.do_request(query, features=features)
  1476. assert response.status_code == 200, response.content
  1477. data = response.data["data"]
  1478. assert len(data) == 1
  1479. assert data[0]["count_unique_user_display"] == 1
  1480. def test_orderby_user_display(self):
  1481. project1 = self.create_project()
  1482. project2 = self.create_project()
  1483. self.store_event(
  1484. data={
  1485. "event_id": "a" * 32,
  1486. "transaction": "/example",
  1487. "message": "how to make fast",
  1488. "timestamp": self.two_min_ago,
  1489. "user": {"email": "cathy@example.com"},
  1490. },
  1491. project_id=project1.id,
  1492. )
  1493. self.store_event(
  1494. data={
  1495. "event_id": "b" * 32,
  1496. "transaction": "/example",
  1497. "message": "how to make fast",
  1498. "timestamp": self.two_min_ago,
  1499. "user": {"username": "catherine"},
  1500. },
  1501. project_id=project2.id,
  1502. )
  1503. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1504. query = {
  1505. "field": ["event.type", "user.display"],
  1506. "query": "user.display:cath*",
  1507. "statsPeriod": "24h",
  1508. "orderby": "-user.display",
  1509. }
  1510. response = self.do_request(query, features=features)
  1511. assert response.status_code == 200, response.content
  1512. data = response.data["data"]
  1513. assert len(data) == 2
  1514. result = [r["user.display"] for r in data]
  1515. # because we're ordering by `-user.display`, we expect the results in reverse sorted order
  1516. assert result == ["cathy@example.com", "catherine"]
  1517. def test_orderby_user_display_with_aggregates(self):
  1518. project1 = self.create_project()
  1519. project2 = self.create_project()
  1520. self.store_event(
  1521. data={
  1522. "event_id": "a" * 32,
  1523. "transaction": "/example",
  1524. "message": "how to make fast",
  1525. "timestamp": self.two_min_ago,
  1526. "user": {"email": "cathy@example.com"},
  1527. },
  1528. project_id=project1.id,
  1529. )
  1530. self.store_event(
  1531. data={
  1532. "event_id": "b" * 32,
  1533. "transaction": "/example",
  1534. "message": "how to make fast",
  1535. "timestamp": self.two_min_ago,
  1536. "user": {"username": "catherine"},
  1537. },
  1538. project_id=project2.id,
  1539. )
  1540. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1541. query = {
  1542. "field": ["event.type", "user.display", "count_unique(title)"],
  1543. "query": "user.display:cath*",
  1544. "statsPeriod": "24h",
  1545. "orderby": "user.display",
  1546. }
  1547. response = self.do_request(query, features=features)
  1548. assert response.status_code == 200, response.content
  1549. data = response.data["data"]
  1550. assert len(data) == 2
  1551. result = [r["user.display"] for r in data]
  1552. # because we're ordering by `user.display`, we expect the results in sorted order
  1553. assert result == ["catherine", "cathy@example.com"]
  1554. def test_has_transaction_status(self):
  1555. project = self.create_project()
  1556. data = load_data("transaction", timestamp=before_now(minutes=1))
  1557. data["transaction"] = "/transactionstatus/1"
  1558. self.store_event(data, project_id=project.id)
  1559. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1560. query = {
  1561. "field": ["event.type", "count(id)"],
  1562. "query": "event.type:transaction has:transaction.status",
  1563. "sort": "-count(id)",
  1564. "statsPeriod": "24h",
  1565. }
  1566. response = self.do_request(query, features=features)
  1567. assert response.status_code == 200, response.content
  1568. data = response.data["data"]
  1569. assert len(data) == 1
  1570. assert data[0]["count_id"] == 1
  1571. def test_not_has_transaction_status(self):
  1572. project = self.create_project()
  1573. data = load_data("transaction", timestamp=before_now(minutes=1))
  1574. data["transaction"] = "/transactionstatus/1"
  1575. self.store_event(data, project_id=project.id)
  1576. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1577. query = {
  1578. "field": ["event.type", "count(id)"],
  1579. "query": "event.type:transaction !has:transaction.status",
  1580. "sort": "-count(id)",
  1581. "statsPeriod": "24h",
  1582. }
  1583. response = self.do_request(query, features=features)
  1584. assert response.status_code == 200, response.content
  1585. data = response.data["data"]
  1586. assert len(data) == 1
  1587. assert data[0]["count_id"] == 0
  1588. def test_tag_that_looks_like_aggregation(self):
  1589. project = self.create_project()
  1590. data = {
  1591. "message": "Failure state",
  1592. "timestamp": self.two_min_ago,
  1593. "tags": {"count_diff": 99},
  1594. }
  1595. self.store_event(data, project_id=project.id)
  1596. query = {
  1597. "field": ["message", "count_diff", "count()"],
  1598. "query": "",
  1599. "project": [project.id],
  1600. "statsPeriod": "24h",
  1601. }
  1602. response = self.do_request(query)
  1603. assert response.status_code == 200, response.content
  1604. meta = response.data["meta"]
  1605. assert "string" == meta["count_diff"], "tags should not be counted as integers"
  1606. assert "string" == meta["message"]
  1607. assert "integer" == meta["count"]
  1608. assert 1 == len(response.data["data"])
  1609. data = response.data["data"][0]
  1610. assert "99" == data["count_diff"]
  1611. assert "Failure state" == data["message"]
  1612. assert 1 == data["count"]
  1613. def test_aggregate_negation(self):
  1614. project = self.create_project()
  1615. data = load_data(
  1616. "transaction",
  1617. timestamp=before_now(minutes=1),
  1618. start_timestamp=before_now(minutes=1, seconds=5),
  1619. )
  1620. self.store_event(data, project_id=project.id)
  1621. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1622. query = {
  1623. "field": ["event.type", "p99()"],
  1624. "query": "event.type:transaction p99():5s",
  1625. "statsPeriod": "24h",
  1626. }
  1627. response = self.do_request(query, features=features)
  1628. assert response.status_code == 200, response.content
  1629. data = response.data["data"]
  1630. assert len(data) == 1
  1631. query = {
  1632. "field": ["event.type", "p99()"],
  1633. "query": "event.type:transaction !p99():5s",
  1634. "statsPeriod": "24h",
  1635. }
  1636. response = self.do_request(query, features=features)
  1637. assert response.status_code == 200, response.content
  1638. data = response.data["data"]
  1639. assert len(data) == 0
  1640. def test_all_aggregates_in_columns(self):
  1641. project = self.create_project()
  1642. data = load_data(
  1643. "transaction",
  1644. timestamp=before_now(minutes=2),
  1645. start_timestamp=before_now(minutes=2, seconds=5),
  1646. )
  1647. data["transaction"] = "/failure_rate/1"
  1648. self.store_event(data, project_id=project.id)
  1649. data = load_data(
  1650. "transaction",
  1651. timestamp=before_now(minutes=1),
  1652. start_timestamp=before_now(minutes=1, seconds=5),
  1653. )
  1654. data["transaction"] = "/failure_rate/1"
  1655. data["contexts"]["trace"]["status"] = "unauthenticated"
  1656. event = self.store_event(data, project_id=project.id)
  1657. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1658. query = {
  1659. "field": [
  1660. "event.type",
  1661. "p50()",
  1662. "p75()",
  1663. "p95()",
  1664. "p99()",
  1665. "p100()",
  1666. "percentile(transaction.duration, 0.99)",
  1667. "apdex(300)",
  1668. "user_misery(300)",
  1669. "failure_rate()",
  1670. ],
  1671. "query": "event.type:transaction",
  1672. }
  1673. response = self.do_request(query, features=features)
  1674. assert response.status_code == 200, response.content
  1675. meta = response.data["meta"]
  1676. assert meta["p50"] == "duration"
  1677. assert meta["p75"] == "duration"
  1678. assert meta["p95"] == "duration"
  1679. assert meta["p99"] == "duration"
  1680. assert meta["p100"] == "duration"
  1681. assert meta["percentile_transaction_duration_0_99"] == "duration"
  1682. assert meta["apdex_300"] == "number"
  1683. assert meta["failure_rate"] == "percentage"
  1684. assert meta["user_misery_300"] == "number"
  1685. data = response.data["data"]
  1686. assert len(data) == 1
  1687. assert data[0]["p50"] == 5000
  1688. assert data[0]["p75"] == 5000
  1689. assert data[0]["p95"] == 5000
  1690. assert data[0]["p99"] == 5000
  1691. assert data[0]["p100"] == 5000
  1692. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1693. assert data[0]["apdex_300"] == 0.0
  1694. assert data[0]["user_misery_300"] == 1
  1695. assert data[0]["failure_rate"] == 0.5
  1696. query = {
  1697. "field": ["event.type", "last_seen()", "latest_event()"],
  1698. "query": "event.type:transaction",
  1699. }
  1700. response = self.do_request(query, features=features)
  1701. assert response.status_code == 200, response.content
  1702. data = response.data["data"]
  1703. assert len(data) == 1
  1704. assert iso_format(before_now(minutes=1))[:-5] in data[0]["last_seen"]
  1705. assert data[0]["latest_event"] == event.event_id
  1706. query = {
  1707. "field": [
  1708. "event.type",
  1709. "count()",
  1710. "count(id)",
  1711. "count_unique(project)",
  1712. "min(transaction.duration)",
  1713. "max(transaction.duration)",
  1714. "avg(transaction.duration)",
  1715. "sum(transaction.duration)",
  1716. ],
  1717. "query": "event.type:transaction",
  1718. }
  1719. response = self.do_request(query, features=features)
  1720. assert response.status_code == 200, response.content
  1721. data = response.data["data"]
  1722. assert len(data) == 1
  1723. assert data[0]["count"] == 2
  1724. assert data[0]["count_id"] == 2
  1725. assert data[0]["count_unique_project"] == 1
  1726. assert data[0]["min_transaction_duration"] == 5000
  1727. assert data[0]["max_transaction_duration"] == 5000
  1728. assert data[0]["avg_transaction_duration"] == 5000
  1729. assert data[0]["sum_transaction_duration"] == 10000
  1730. def test_all_aggregates_in_query(self):
  1731. project = self.create_project()
  1732. data = load_data(
  1733. "transaction",
  1734. timestamp=before_now(minutes=2),
  1735. start_timestamp=before_now(minutes=2, seconds=5),
  1736. )
  1737. data["transaction"] = "/failure_rate/1"
  1738. self.store_event(data, project_id=project.id)
  1739. data = load_data(
  1740. "transaction",
  1741. timestamp=before_now(minutes=1),
  1742. start_timestamp=before_now(minutes=1, seconds=5),
  1743. )
  1744. data["transaction"] = "/failure_rate/2"
  1745. data["contexts"]["trace"]["status"] = "unauthenticated"
  1746. self.store_event(data, project_id=project.id)
  1747. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1748. query = {
  1749. "field": [
  1750. "event.type",
  1751. "p50()",
  1752. "p75()",
  1753. "p95()",
  1754. "percentile(transaction.duration, 0.99)",
  1755. "p100()",
  1756. ],
  1757. "query": "event.type:transaction p50():>100 p75():>1000 p95():>1000 p100():>1000 percentile(transaction.duration, 0.99):>1000",
  1758. }
  1759. response = self.do_request(query, features=features)
  1760. assert response.status_code == 200, response.content
  1761. data = response.data["data"]
  1762. assert len(data) == 1
  1763. assert data[0]["p50"] == 5000
  1764. assert data[0]["p75"] == 5000
  1765. assert data[0]["p95"] == 5000
  1766. assert data[0]["p100"] == 5000
  1767. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1768. query = {
  1769. "field": ["event.type", "apdex(300)", "user_misery(300)", "failure_rate()"],
  1770. "query": "event.type:transaction apdex(300):>-1.0 failure_rate():>0.25",
  1771. }
  1772. response = self.do_request(query, features=features)
  1773. assert response.status_code == 200, response.content
  1774. data = response.data["data"]
  1775. assert len(data) == 1
  1776. assert data[0]["apdex_300"] == 0.0
  1777. assert data[0]["user_misery_300"] == 1
  1778. assert data[0]["failure_rate"] == 0.5
  1779. query = {
  1780. "field": ["event.type", "last_seen()", "latest_event()"],
  1781. "query": u"event.type:transaction last_seen():>1990-12-01T00:00:00",
  1782. }
  1783. response = self.do_request(query, features=features)
  1784. assert response.status_code == 200, response.content
  1785. data = response.data["data"]
  1786. assert len(data) == 1
  1787. query = {
  1788. "field": ["event.type", "count()", "count(id)", "count_unique(transaction)"],
  1789. "query": "event.type:transaction count():>1 count(id):>1 count_unique(transaction):>1",
  1790. }
  1791. response = self.do_request(query, features=features)
  1792. assert response.status_code == 200, response.content
  1793. data = response.data["data"]
  1794. assert len(data) == 1
  1795. assert data[0]["count"] == 2
  1796. assert data[0]["count_id"] == 2
  1797. assert data[0]["count_unique_transaction"] == 2
  1798. query = {
  1799. "field": [
  1800. "event.type",
  1801. "min(transaction.duration)",
  1802. "max(transaction.duration)",
  1803. "avg(transaction.duration)",
  1804. "sum(transaction.duration)",
  1805. ],
  1806. "query": "event.type:transaction min(transaction.duration):>1000 max(transaction.duration):>1000 avg(transaction.duration):>1000 sum(transaction.duration):>1000",
  1807. }
  1808. response = self.do_request(query, features=features)
  1809. assert response.status_code == 200, response.content
  1810. data = response.data["data"]
  1811. assert len(data) == 1
  1812. assert data[0]["min_transaction_duration"] == 5000
  1813. assert data[0]["max_transaction_duration"] == 5000
  1814. assert data[0]["avg_transaction_duration"] == 5000
  1815. assert data[0]["sum_transaction_duration"] == 10000
  1816. query = {
  1817. "field": ["event.type", "apdex(400)"],
  1818. "query": "event.type:transaction apdex(400):0",
  1819. }
  1820. response = self.do_request(query, features=features)
  1821. assert response.status_code == 200, response.content
  1822. data = response.data["data"]
  1823. assert len(data) == 1
  1824. assert data[0]["apdex_400"] == 0
  1825. def test_functions_in_orderby(self):
  1826. project = self.create_project()
  1827. data = load_data(
  1828. "transaction",
  1829. timestamp=before_now(minutes=2),
  1830. start_timestamp=before_now(minutes=2, seconds=5),
  1831. )
  1832. data["transaction"] = "/failure_rate/1"
  1833. self.store_event(data, project_id=project.id)
  1834. data = load_data(
  1835. "transaction",
  1836. timestamp=before_now(minutes=1),
  1837. start_timestamp=before_now(minutes=1, seconds=5),
  1838. )
  1839. data["transaction"] = "/failure_rate/2"
  1840. data["contexts"]["trace"]["status"] = "unauthenticated"
  1841. event = self.store_event(data, project_id=project.id)
  1842. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1843. query = {
  1844. "field": ["event.type", "p75()"],
  1845. "sort": "-p75",
  1846. "query": "event.type:transaction",
  1847. }
  1848. response = self.do_request(query, features=features)
  1849. assert response.status_code == 200, response.content
  1850. data = response.data["data"]
  1851. assert len(data) == 1
  1852. assert data[0]["p75"] == 5000
  1853. query = {
  1854. "field": ["event.type", "percentile(transaction.duration, 0.99)"],
  1855. "sort": "-percentile_transaction_duration_0_99",
  1856. "query": "event.type:transaction",
  1857. }
  1858. response = self.do_request(query, features=features)
  1859. assert response.status_code == 200, response.content
  1860. data = response.data["data"]
  1861. assert len(data) == 1
  1862. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1863. query = {
  1864. "field": ["event.type", "apdex(300)"],
  1865. "sort": "-apdex(300)",
  1866. "query": "event.type:transaction",
  1867. }
  1868. response = self.do_request(query, features=features)
  1869. assert response.status_code == 200, response.content
  1870. data = response.data["data"]
  1871. assert len(data) == 1
  1872. assert data[0]["apdex_300"] == 0.0
  1873. query = {
  1874. "field": ["event.type", "latest_event()"],
  1875. "query": u"event.type:transaction",
  1876. "sort": "latest_event",
  1877. }
  1878. response = self.do_request(query, features=features)
  1879. assert response.status_code == 200, response.content
  1880. data = response.data["data"]
  1881. assert len(data) == 1
  1882. assert data[0]["latest_event"] == event.event_id
  1883. query = {
  1884. "field": ["event.type", "count_unique(transaction)"],
  1885. "query": "event.type:transaction",
  1886. "sort": "-count_unique_transaction",
  1887. }
  1888. response = self.do_request(query, features=features)
  1889. assert response.status_code == 200, response.content
  1890. data = response.data["data"]
  1891. assert len(data) == 1
  1892. assert data[0]["count_unique_transaction"] == 2
  1893. query = {
  1894. "field": ["event.type", "min(transaction.duration)"],
  1895. "query": "event.type:transaction",
  1896. "sort": "-min_transaction_duration",
  1897. }
  1898. response = self.do_request(query, features=features)
  1899. assert response.status_code == 200, response.content
  1900. data = response.data["data"]
  1901. assert len(data) == 1
  1902. assert data[0]["min_transaction_duration"] == 5000
  1903. def test_issue_alias_in_aggregate(self):
  1904. project = self.create_project()
  1905. self.store_event(
  1906. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1907. project_id=project.id,
  1908. )
  1909. self.store_event(
  1910. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  1911. project_id=project.id,
  1912. )
  1913. query = {
  1914. "field": ["event.type", "count_unique(issue)"],
  1915. "query": "count_unique(issue):>1",
  1916. }
  1917. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1918. response = self.do_request(query, features=features)
  1919. assert response.status_code == 200, response.content
  1920. data = response.data["data"]
  1921. assert len(data) == 1
  1922. assert data[0]["count_unique_issue"] == 2
  1923. def test_deleted_issue_in_results(self):
  1924. project = self.create_project()
  1925. event1 = self.store_event(
  1926. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1927. project_id=project.id,
  1928. )
  1929. event2 = self.store_event(
  1930. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  1931. project_id=project.id,
  1932. )
  1933. event2.group.delete()
  1934. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1935. query = {"field": ["issue", "count()"], "sort": "issue"}
  1936. response = self.do_request(query, features=features)
  1937. assert response.status_code == 200, response.content
  1938. data = response.data["data"]
  1939. assert len(data) == 2
  1940. assert data[0]["issue"] == event1.group.qualified_short_id
  1941. assert data[1]["issue"] == "unknown"
  1942. def test_last_seen_negative_duration(self):
  1943. project = self.create_project()
  1944. self.store_event(
  1945. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1946. project_id=project.id,
  1947. )
  1948. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1949. query = {"field": ["id", "last_seen()"], "query": "last_seen():-30d"}
  1950. response = self.do_request(query, features=features)
  1951. assert response.status_code == 200, response.content
  1952. data = response.data["data"]
  1953. assert len(data) == 1
  1954. assert data[0]["id"] == "f" * 32
  1955. def test_last_seen_aggregate_condition(self):
  1956. project = self.create_project()
  1957. self.store_event(
  1958. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1959. project_id=project.id,
  1960. )
  1961. query = {
  1962. "field": ["id", "last_seen()"],
  1963. "query": "last_seen():>{}".format(iso_format(before_now(days=30))),
  1964. }
  1965. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1966. response = self.do_request(query, features=features)
  1967. assert response.status_code == 200, response.content
  1968. data = response.data["data"]
  1969. assert len(data) == 1
  1970. assert data[0]["id"] == "f" * 32
  1971. def test_conditional_filter(self):
  1972. project = self.create_project()
  1973. for v in ["a", "b"]:
  1974. self.store_event(
  1975. data={
  1976. "event_id": v * 32,
  1977. "timestamp": self.two_min_ago,
  1978. "fingerprint": ["group_1"],
  1979. },
  1980. project_id=project.id,
  1981. )
  1982. query = {
  1983. "field": ["id"],
  1984. "query": "id:{} OR id:{}".format("a" * 32, "b" * 32),
  1985. "orderby": "id",
  1986. }
  1987. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1988. response = self.do_request(query, features=features)
  1989. assert response.status_code == 200, response.content
  1990. data = response.data["data"]
  1991. assert len(data) == 2
  1992. assert data[0]["id"] == "a" * 32
  1993. assert data[1]["id"] == "b" * 32
  1994. def test_aggregation_comparison_with_conditional_filter(self):
  1995. project = self.create_project()
  1996. self.store_event(
  1997. data={
  1998. "event_id": "a" * 32,
  1999. "timestamp": self.min_ago,
  2000. "fingerprint": ["group_1"],
  2001. "user": {"email": "foo@example.com"},
  2002. "environment": "prod",
  2003. },
  2004. project_id=project.id,
  2005. )
  2006. self.store_event(
  2007. data={
  2008. "event_id": "b" * 32,
  2009. "timestamp": self.min_ago,
  2010. "fingerprint": ["group_2"],
  2011. "user": {"email": "foo@example.com"},
  2012. "environment": "staging",
  2013. },
  2014. project_id=project.id,
  2015. )
  2016. event = self.store_event(
  2017. data={
  2018. "event_id": "c" * 32,
  2019. "timestamp": self.min_ago,
  2020. "fingerprint": ["group_2"],
  2021. "user": {"email": "foo@example.com"},
  2022. "environment": "prod",
  2023. },
  2024. project_id=project.id,
  2025. )
  2026. self.store_event(
  2027. data={
  2028. "event_id": "d" * 32,
  2029. "timestamp": self.min_ago,
  2030. "fingerprint": ["group_2"],
  2031. "user": {"email": "foo@example.com"},
  2032. "environment": "canary",
  2033. },
  2034. project_id=project.id,
  2035. )
  2036. query = {
  2037. "field": ["issue.id", "count(id)"],
  2038. "query": "count(id):>1 user.email:foo@example.com AND (environment:prod OR environment:staging)",
  2039. "orderby": "issue.id",
  2040. }
  2041. response = self.do_request(query)
  2042. assert response.status_code == 200, response.content
  2043. assert len(response.data["data"]) == 1
  2044. data = response.data["data"]
  2045. assert data[0]["issue.id"] == event.group_id
  2046. assert data[0]["count_id"] == 2
  2047. def test_messed_up_function_values(self):
  2048. # TODO (evanh): It would be nice if this surfaced an error to the user.
  2049. # The problem: The && causes the parser to treat that term not as a bad
  2050. # function call but a valid raw search with parens in it. It's not trivial
  2051. # to change the parser to recognize "bad function values" and surface them.
  2052. project = self.create_project()
  2053. for v in ["a", "b"]:
  2054. self.store_event(
  2055. data={
  2056. "event_id": v * 32,
  2057. "timestamp": self.two_min_ago,
  2058. "fingerprint": ["group_1"],
  2059. },
  2060. project_id=project.id,
  2061. )
  2062. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2063. query = {
  2064. "field": [
  2065. "transaction",
  2066. "project",
  2067. "epm()",
  2068. "p50()",
  2069. "p95()",
  2070. "failure_rate()",
  2071. "apdex(300)",
  2072. "count_unique(user)",
  2073. "user_misery(300)",
  2074. ],
  2075. "query": "failure_rate():>0.003&& users:>10 event.type:transaction",
  2076. "sort": "-failure_rate",
  2077. "statsPeriod": "24h",
  2078. }
  2079. response = self.do_request(query, features=features)
  2080. assert response.status_code == 200, response.content
  2081. data = response.data["data"]
  2082. assert len(data) == 0
  2083. def test_context_fields_between_datasets(self):
  2084. project = self.create_project()
  2085. event_data = load_data("android")
  2086. transaction_data = load_data("transaction")
  2087. event_data["spans"] = transaction_data["spans"]
  2088. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  2089. event_data["type"] = "transaction"
  2090. event_data["transaction"] = "/failure_rate/1"
  2091. event_data["timestamp"] = iso_format(before_now(minutes=1))
  2092. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  2093. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  2094. self.store_event(event_data, project_id=project.id)
  2095. event_data["type"] = "error"
  2096. self.store_event(event_data, project_id=project.id)
  2097. fields = [
  2098. "os.build",
  2099. "os.kernel_version",
  2100. "device.arch",
  2101. "device.battery_level",
  2102. "device.brand",
  2103. "device.charging",
  2104. "device.locale",
  2105. "device.model_id",
  2106. "device.name",
  2107. "device.online",
  2108. "device.orientation",
  2109. "device.simulator",
  2110. "device.uuid",
  2111. ]
  2112. data = [
  2113. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  2114. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  2115. ]
  2116. for datum in data:
  2117. response = self.do_request(datum)
  2118. assert response.status_code == 200, response.content
  2119. assert len(response.data["data"]) == 1, datum
  2120. results = response.data["data"]
  2121. assert results[0]["count"] == 1, datum
  2122. for field in fields:
  2123. key, value = field.split(".", 1)
  2124. expected = six.text_type(event_data["contexts"][key][value])
  2125. assert results[0][field] == expected, field + six.text_type(datum)
  2126. def test_http_fields_between_datasets(self):
  2127. project = self.create_project()
  2128. event_data = load_data("android")
  2129. transaction_data = load_data("transaction")
  2130. event_data["spans"] = transaction_data["spans"]
  2131. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  2132. event_data["type"] = "transaction"
  2133. event_data["transaction"] = "/failure_rate/1"
  2134. event_data["timestamp"] = iso_format(before_now(minutes=1))
  2135. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  2136. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  2137. event_data["request"] = transaction_data["request"]
  2138. self.store_event(event_data, project_id=project.id)
  2139. event_data["type"] = "error"
  2140. self.store_event(event_data, project_id=project.id)
  2141. fields = [
  2142. "http.method",
  2143. "http.referer",
  2144. "http.url",
  2145. ]
  2146. expected = [
  2147. "GET",
  2148. "fixtures.transaction",
  2149. "http://countries:8010/country_by_code/",
  2150. ]
  2151. data = [
  2152. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  2153. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  2154. ]
  2155. for datum in data:
  2156. response = self.do_request(datum)
  2157. assert response.status_code == 200, response.content
  2158. assert len(response.data["data"]) == 1, datum
  2159. results = response.data["data"]
  2160. assert results[0]["count"] == 1, datum
  2161. for (field, exp) in zip(fields, expected):
  2162. assert results[0][field] == exp, field + six.text_type(datum)
  2163. def test_histogram_function(self):
  2164. project = self.create_project()
  2165. start = before_now(minutes=2).replace(microsecond=0)
  2166. latencies = [
  2167. (1, 500, 5),
  2168. (1000, 1500, 4),
  2169. (3000, 3500, 3),
  2170. (6000, 6500, 2),
  2171. (10000, 10000, 1), # just to make the math easy
  2172. ]
  2173. values = []
  2174. for bucket in latencies:
  2175. for i in range(bucket[2]):
  2176. # Don't generate a wide range of variance as the buckets can mis-align.
  2177. milliseconds = random.randint(bucket[0], bucket[1])
  2178. values.append(milliseconds)
  2179. data = load_data("transaction")
  2180. data["transaction"] = "/failure_rate/{}".format(milliseconds)
  2181. data["timestamp"] = iso_format(start)
  2182. data["start_timestamp"] = (start - timedelta(milliseconds=milliseconds)).isoformat()
  2183. self.store_event(data, project_id=project.id)
  2184. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2185. query = {
  2186. "field": ["histogram(transaction.duration, 10)", "count()"],
  2187. "query": "event.type:transaction",
  2188. "sort": "histogram_transaction_duration_10",
  2189. }
  2190. response = self.do_request(query, features=features)
  2191. assert response.status_code == 200, response.content
  2192. data = response.data["data"]
  2193. assert len(data) == 11
  2194. bucket_size = ceil((max(values) - min(values)) / 10.0)
  2195. expected = [
  2196. (0, 5),
  2197. (bucket_size, 4),
  2198. (bucket_size * 2, 0),
  2199. (bucket_size * 3, 3),
  2200. (bucket_size * 4, 0),
  2201. (bucket_size * 5, 0),
  2202. (bucket_size * 6, 2),
  2203. (bucket_size * 7, 0),
  2204. (bucket_size * 8, 0),
  2205. (bucket_size * 9, 0),
  2206. (bucket_size * 10, 1),
  2207. ]
  2208. for idx, datum in enumerate(data):
  2209. assert datum["histogram_transaction_duration_10"] == expected[idx][0]
  2210. assert datum["count"] == expected[idx][1]
  2211. def test_histogram_function_with_filters(self):
  2212. project = self.create_project()
  2213. start = before_now(minutes=2).replace(microsecond=0)
  2214. latencies = [
  2215. (1, 500, 5),
  2216. (1000, 1500, 4),
  2217. (3000, 3500, 3),
  2218. (6000, 6500, 2),
  2219. (10000, 10000, 1), # just to make the math easy
  2220. ]
  2221. values = []
  2222. for bucket in latencies:
  2223. for i in range(bucket[2]):
  2224. milliseconds = random.randint(bucket[0], bucket[1])
  2225. values.append(milliseconds)
  2226. data = load_data("transaction")
  2227. data["transaction"] = "/failure_rate/sleepy_gary/{}".format(milliseconds)
  2228. data["timestamp"] = iso_format(start)
  2229. data["start_timestamp"] = (start - timedelta(milliseconds=milliseconds)).isoformat()
  2230. self.store_event(data, project_id=project.id)
  2231. # Add a transaction that totally throws off the buckets
  2232. milliseconds = random.randint(bucket[0], bucket[1])
  2233. data = load_data("transaction")
  2234. data["transaction"] = "/failure_rate/hamurai"
  2235. data["timestamp"] = iso_format(start)
  2236. data["start_timestamp"] = iso_format(start - timedelta(milliseconds=1000000))
  2237. self.store_event(data, project_id=project.id)
  2238. query = {
  2239. "field": ["histogram(transaction.duration, 10)", "count()"],
  2240. "query": "event.type:transaction transaction:/failure_rate/sleepy_gary*",
  2241. "sort": "histogram_transaction_duration_10",
  2242. }
  2243. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2244. response = self.do_request(query, features=features)
  2245. assert response.status_code == 200, response.content
  2246. data = response.data["data"]
  2247. assert len(data) == 11
  2248. bucket_size = ceil((max(values) - min(values)) / 10.0)
  2249. expected = [
  2250. (0, 5),
  2251. (bucket_size, 4),
  2252. (bucket_size * 2, 0),
  2253. (bucket_size * 3, 3),
  2254. (bucket_size * 4, 0),
  2255. (bucket_size * 5, 0),
  2256. (bucket_size * 6, 2),
  2257. (bucket_size * 7, 0),
  2258. (bucket_size * 8, 0),
  2259. (bucket_size * 9, 0),
  2260. (bucket_size * 10, 1),
  2261. ]
  2262. for idx, datum in enumerate(data):
  2263. assert datum["histogram_transaction_duration_10"] == expected[idx][0]
  2264. assert datum["count"] == expected[idx][1]
  2265. @mock.patch("sentry.utils.snuba.quantize_time")
  2266. def test_quantize_dates(self, mock_quantize):
  2267. self.create_project()
  2268. mock_quantize.return_value = before_now(days=1).replace(tzinfo=utc)
  2269. # Don't quantize short time periods
  2270. query = {"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]}
  2271. self.do_request(query)
  2272. # Don't quantize absolute date periods
  2273. self.do_request(query)
  2274. query = {
  2275. "start": iso_format(before_now(days=20)),
  2276. "end": iso_format(before_now(days=15)),
  2277. "query": "",
  2278. "field": ["id", "timestamp"],
  2279. }
  2280. self.do_request(query)
  2281. assert len(mock_quantize.mock_calls) == 0
  2282. # Quantize long date periods
  2283. query = {"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""}
  2284. self.do_request(query)
  2285. assert len(mock_quantize.mock_calls) == 2
  2286. def test_limit_number_of_fields(self):
  2287. self.create_project()
  2288. for i in range(1, 25):
  2289. response = self.do_request({"field": ["id"] * i})
  2290. if i <= 20:
  2291. assert response.status_code == 200
  2292. else:
  2293. assert response.status_code == 400
  2294. assert (
  2295. response.data["detail"]
  2296. == "You can view up to 20 fields at a time. Please delete some and try again."
  2297. )