test_organization_events_v2.py 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512
  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_stack_wildcard_condition(self):
  1119. project = self.create_project()
  1120. data = load_data("javascript")
  1121. data["timestamp"] = self.min_ago
  1122. self.store_event(data=data, project_id=project.id)
  1123. query = {"field": ["stack.filename", "message"], "query": "stack.filename:*.js"}
  1124. response = self.do_request(query)
  1125. assert response.status_code == 200, response.content
  1126. assert len(response.data["data"]) == 1
  1127. assert response.data["meta"]["message"] == "string"
  1128. def test_email_wildcard_condition(self):
  1129. project = self.create_project()
  1130. data = load_data("javascript")
  1131. data["timestamp"] = self.min_ago
  1132. self.store_event(data=data, project_id=project.id)
  1133. query = {"field": ["stack.filename", "message"], "query": "user.email:*@example.org"}
  1134. response = self.do_request(query)
  1135. assert response.status_code == 200, response.content
  1136. assert len(response.data["data"]) == 1
  1137. assert response.data["meta"]["message"] == "string"
  1138. def test_transaction_event_type(self):
  1139. project = self.create_project()
  1140. data = load_data(
  1141. "transaction",
  1142. timestamp=before_now(minutes=1),
  1143. start_timestamp=before_now(minutes=1, seconds=5),
  1144. )
  1145. self.store_event(data=data, project_id=project.id)
  1146. query = {
  1147. "field": ["transaction", "transaction.duration", "transaction.status"],
  1148. "query": "event.type:transaction",
  1149. }
  1150. response = self.do_request(query)
  1151. assert response.status_code == 200, response.content
  1152. assert len(response.data["data"]) == 1
  1153. assert response.data["meta"]["transaction.duration"] == "duration"
  1154. assert response.data["meta"]["transaction.status"] == "string"
  1155. assert response.data["data"][0]["transaction.status"] == "ok"
  1156. def test_trace_columns(self):
  1157. project = self.create_project()
  1158. data = load_data(
  1159. "transaction",
  1160. timestamp=before_now(minutes=1),
  1161. start_timestamp=before_now(minutes=1, seconds=5),
  1162. )
  1163. self.store_event(data=data, project_id=project.id)
  1164. query = {"field": ["trace"], "query": "event.type:transaction"}
  1165. response = self.do_request(query)
  1166. assert response.status_code == 200, response.content
  1167. assert len(response.data["data"]) == 1
  1168. assert response.data["meta"]["trace"] == "string"
  1169. assert response.data["data"][0]["trace"] == data["contexts"]["trace"]["trace_id"]
  1170. def test_issue_in_columns(self):
  1171. project1 = self.create_project()
  1172. project2 = self.create_project()
  1173. event1 = self.store_event(
  1174. data={
  1175. "event_id": "a" * 32,
  1176. "transaction": "/example",
  1177. "message": "how to make fast",
  1178. "timestamp": self.two_min_ago,
  1179. "fingerprint": ["group_1"],
  1180. },
  1181. project_id=project1.id,
  1182. )
  1183. event2 = self.store_event(
  1184. data={
  1185. "event_id": "b" * 32,
  1186. "transaction": "/example",
  1187. "message": "how to make fast",
  1188. "timestamp": self.two_min_ago,
  1189. "fingerprint": ["group_1"],
  1190. },
  1191. project_id=project2.id,
  1192. )
  1193. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1194. query = {"field": ["id", "issue"], "orderby": ["id"]}
  1195. response = self.do_request(query, features=features)
  1196. assert response.status_code == 200, response.content
  1197. data = response.data["data"]
  1198. assert len(data) == 2
  1199. assert data[0]["id"] == event1.event_id
  1200. assert data[0]["issue.id"] == event1.group_id
  1201. assert data[0]["issue"] == event1.group.qualified_short_id
  1202. assert data[1]["id"] == event2.event_id
  1203. assert data[1]["issue.id"] == event2.group_id
  1204. assert data[1]["issue"] == event2.group.qualified_short_id
  1205. def test_issue_in_search_and_columns(self):
  1206. project1 = self.create_project()
  1207. project2 = self.create_project()
  1208. event1 = self.store_event(
  1209. data={
  1210. "event_id": "a" * 32,
  1211. "transaction": "/example",
  1212. "message": "how to make fast",
  1213. "timestamp": self.two_min_ago,
  1214. "fingerprint": ["group_1"],
  1215. },
  1216. project_id=project1.id,
  1217. )
  1218. self.store_event(
  1219. data={
  1220. "event_id": "b" * 32,
  1221. "transaction": "/example",
  1222. "message": "how to make fast",
  1223. "timestamp": self.two_min_ago,
  1224. "fingerprint": ["group_1"],
  1225. },
  1226. project_id=project2.id,
  1227. )
  1228. tests = [
  1229. ("issue", "issue:%s" % event1.group.qualified_short_id),
  1230. ("issue.id", "issue:%s" % event1.group.qualified_short_id),
  1231. ("issue", "issue.id:%s" % event1.group_id),
  1232. ("issue.id", "issue.id:%s" % event1.group_id),
  1233. ]
  1234. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1235. for testdata in tests:
  1236. query = {"field": [testdata[0]], "query": testdata[1]}
  1237. response = self.do_request(query, features=features)
  1238. assert response.status_code == 200, response.content
  1239. data = response.data["data"]
  1240. assert len(data) == 1
  1241. assert data[0]["id"] == event1.event_id
  1242. assert data[0]["issue.id"] == event1.group_id
  1243. if testdata[0] == "issue":
  1244. assert data[0]["issue"] == event1.group.qualified_short_id
  1245. else:
  1246. assert data[0].get("issue", None) is None
  1247. def test_issue_negation(self):
  1248. project1 = self.create_project()
  1249. project2 = self.create_project()
  1250. event1 = self.store_event(
  1251. data={
  1252. "event_id": "a" * 32,
  1253. "transaction": "/example",
  1254. "message": "how to make fast",
  1255. "timestamp": self.two_min_ago,
  1256. "fingerprint": ["group_1"],
  1257. },
  1258. project_id=project1.id,
  1259. )
  1260. event2 = self.store_event(
  1261. data={
  1262. "event_id": "b" * 32,
  1263. "transaction": "/example",
  1264. "message": "go really fast plz",
  1265. "timestamp": self.two_min_ago,
  1266. "fingerprint": ["group_2"],
  1267. },
  1268. project_id=project2.id,
  1269. )
  1270. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1271. query = {
  1272. "field": ["title", "issue.id"],
  1273. "query": "!issue:{}".format(event1.group.qualified_short_id),
  1274. }
  1275. response = self.do_request(query, features=features)
  1276. assert response.status_code == 200, response.content
  1277. data = response.data["data"]
  1278. assert len(data) == 1
  1279. assert data[0]["title"] == event2.title
  1280. assert data[0]["issue.id"] == event2.group_id
  1281. def test_search_for_nonexistent_issue(self):
  1282. project1 = self.create_project()
  1283. self.store_event(
  1284. data={
  1285. "event_id": "a" * 32,
  1286. "transaction": "/example",
  1287. "message": "how to make fast",
  1288. "timestamp": self.two_min_ago,
  1289. "fingerprint": ["group_1"],
  1290. },
  1291. project_id=project1.id,
  1292. )
  1293. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1294. query = {"field": ["count()"], "query": "issue.id:112358"}
  1295. response = self.do_request(query, features=features)
  1296. assert response.status_code == 200, response.content
  1297. data = response.data["data"]
  1298. assert len(data) == 1
  1299. assert data[0]["count"] == 0
  1300. def test_issue_alias_inside_aggregate(self):
  1301. project1 = self.create_project()
  1302. self.store_event(
  1303. data={
  1304. "event_id": "a" * 32,
  1305. "transaction": "/example",
  1306. "message": "how to make fast",
  1307. "timestamp": self.two_min_ago,
  1308. "fingerprint": ["group_1"],
  1309. },
  1310. project_id=project1.id,
  1311. )
  1312. self.store_event(
  1313. data={
  1314. "event_id": "b" * 32,
  1315. "transaction": "/example",
  1316. "message": "how to make fast",
  1317. "timestamp": self.two_min_ago,
  1318. "fingerprint": ["group_2"],
  1319. },
  1320. project_id=project1.id,
  1321. )
  1322. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1323. query = {
  1324. "field": ["project", "count(id)", "count_unique(issue.id)", "count_unique(issue)"],
  1325. "sort": "-count(id)",
  1326. "statsPeriod": "24h",
  1327. }
  1328. response = self.do_request(query, features=features)
  1329. assert response.status_code == 200, response.content
  1330. data = response.data["data"]
  1331. assert len(data) == 1
  1332. assert data[0]["count_id"] == 2
  1333. assert data[0]["count_unique_issue_id"] == 2
  1334. assert data[0]["count_unique_issue"] == 2
  1335. def test_project_alias_inside_aggregate(self):
  1336. project1 = self.create_project()
  1337. project2 = self.create_project()
  1338. self.store_event(
  1339. data={
  1340. "event_id": "a" * 32,
  1341. "transaction": "/example",
  1342. "message": "how to make fast",
  1343. "timestamp": self.two_min_ago,
  1344. "fingerprint": ["group_1"],
  1345. },
  1346. project_id=project1.id,
  1347. )
  1348. self.store_event(
  1349. data={
  1350. "event_id": "b" * 32,
  1351. "transaction": "/example",
  1352. "message": "how to make fast",
  1353. "timestamp": self.two_min_ago,
  1354. "fingerprint": ["group_2"],
  1355. },
  1356. project_id=project2.id,
  1357. )
  1358. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1359. query = {
  1360. "field": [
  1361. "event.type",
  1362. "count(id)",
  1363. "count_unique(project.id)",
  1364. "count_unique(project)",
  1365. ],
  1366. "sort": "-count(id)",
  1367. "statsPeriod": "24h",
  1368. }
  1369. response = self.do_request(query, features=features)
  1370. assert response.status_code == 200, response.content
  1371. data = response.data["data"]
  1372. assert len(data) == 1
  1373. assert data[0]["count_id"] == 2
  1374. assert data[0]["count_unique_project_id"] == 2
  1375. assert data[0]["count_unique_project"] == 2
  1376. def test_user_display(self):
  1377. project1 = self.create_project()
  1378. project2 = self.create_project()
  1379. self.store_event(
  1380. data={
  1381. "event_id": "a" * 32,
  1382. "transaction": "/example",
  1383. "message": "how to make fast",
  1384. "timestamp": self.two_min_ago,
  1385. "user": {"email": "cathy@example.com"},
  1386. },
  1387. project_id=project1.id,
  1388. )
  1389. self.store_event(
  1390. data={
  1391. "event_id": "b" * 32,
  1392. "transaction": "/example",
  1393. "message": "how to make fast",
  1394. "timestamp": self.two_min_ago,
  1395. "user": {"username": "catherine"},
  1396. },
  1397. project_id=project2.id,
  1398. )
  1399. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1400. query = {
  1401. "field": ["event.type", "user.display"],
  1402. "query": "user.display:cath*",
  1403. "statsPeriod": "24h",
  1404. }
  1405. response = self.do_request(query, features=features)
  1406. assert response.status_code == 200, response.content
  1407. data = response.data["data"]
  1408. assert len(data) == 2
  1409. result = set([r["user.display"] for r in data])
  1410. assert result == set(["catherine", "cathy@example.com"])
  1411. def test_user_display_with_aggregates(self):
  1412. self.login_as(user=self.user)
  1413. project1 = self.create_project()
  1414. self.store_event(
  1415. data={
  1416. "event_id": "a" * 32,
  1417. "transaction": "/example",
  1418. "message": "how to make fast",
  1419. "timestamp": self.two_min_ago,
  1420. "user": {"email": "cathy@example.com"},
  1421. },
  1422. project_id=project1.id,
  1423. )
  1424. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1425. query = {
  1426. "field": ["event.type", "user.display", "count_unique(title)"],
  1427. "statsPeriod": "24h",
  1428. }
  1429. response = self.do_request(query, features=features)
  1430. assert response.status_code == 200, response.content
  1431. data = response.data["data"]
  1432. assert len(data) == 1
  1433. result = set([r["user.display"] for r in data])
  1434. assert result == set(["cathy@example.com"])
  1435. query = {"field": ["event.type", "count_unique(user.display)"], "statsPeriod": "24h"}
  1436. response = self.do_request(query, features=features)
  1437. assert response.status_code == 200, response.content
  1438. data = response.data["data"]
  1439. assert len(data) == 1
  1440. assert data[0]["count_unique_user_display"] == 1
  1441. def test_orderby_user_display(self):
  1442. project1 = self.create_project()
  1443. project2 = self.create_project()
  1444. self.store_event(
  1445. data={
  1446. "event_id": "a" * 32,
  1447. "transaction": "/example",
  1448. "message": "how to make fast",
  1449. "timestamp": self.two_min_ago,
  1450. "user": {"email": "cathy@example.com"},
  1451. },
  1452. project_id=project1.id,
  1453. )
  1454. self.store_event(
  1455. data={
  1456. "event_id": "b" * 32,
  1457. "transaction": "/example",
  1458. "message": "how to make fast",
  1459. "timestamp": self.two_min_ago,
  1460. "user": {"username": "catherine"},
  1461. },
  1462. project_id=project2.id,
  1463. )
  1464. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1465. query = {
  1466. "field": ["event.type", "user.display"],
  1467. "query": "user.display:cath*",
  1468. "statsPeriod": "24h",
  1469. "orderby": "-user.display",
  1470. }
  1471. response = self.do_request(query, features=features)
  1472. assert response.status_code == 200, response.content
  1473. data = response.data["data"]
  1474. assert len(data) == 2
  1475. result = [r["user.display"] for r in data]
  1476. # because we're ordering by `-user.display`, we expect the results in reverse sorted order
  1477. assert result == ["cathy@example.com", "catherine"]
  1478. def test_orderby_user_display_with_aggregates(self):
  1479. project1 = self.create_project()
  1480. project2 = self.create_project()
  1481. self.store_event(
  1482. data={
  1483. "event_id": "a" * 32,
  1484. "transaction": "/example",
  1485. "message": "how to make fast",
  1486. "timestamp": self.two_min_ago,
  1487. "user": {"email": "cathy@example.com"},
  1488. },
  1489. project_id=project1.id,
  1490. )
  1491. self.store_event(
  1492. data={
  1493. "event_id": "b" * 32,
  1494. "transaction": "/example",
  1495. "message": "how to make fast",
  1496. "timestamp": self.two_min_ago,
  1497. "user": {"username": "catherine"},
  1498. },
  1499. project_id=project2.id,
  1500. )
  1501. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1502. query = {
  1503. "field": ["event.type", "user.display", "count_unique(title)"],
  1504. "query": "user.display:cath*",
  1505. "statsPeriod": "24h",
  1506. "orderby": "user.display",
  1507. }
  1508. response = self.do_request(query, features=features)
  1509. assert response.status_code == 200, response.content
  1510. data = response.data["data"]
  1511. assert len(data) == 2
  1512. result = [r["user.display"] for r in data]
  1513. # because we're ordering by `user.display`, we expect the results in sorted order
  1514. assert result == ["catherine", "cathy@example.com"]
  1515. def test_has_transaction_status(self):
  1516. project = self.create_project()
  1517. data = load_data("transaction", timestamp=before_now(minutes=1))
  1518. data["transaction"] = "/transactionstatus/1"
  1519. self.store_event(data, project_id=project.id)
  1520. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1521. query = {
  1522. "field": ["event.type", "count(id)"],
  1523. "query": "event.type:transaction has:transaction.status",
  1524. "sort": "-count(id)",
  1525. "statsPeriod": "24h",
  1526. }
  1527. response = self.do_request(query, features=features)
  1528. assert response.status_code == 200, response.content
  1529. data = response.data["data"]
  1530. assert len(data) == 1
  1531. assert data[0]["count_id"] == 1
  1532. def test_not_has_transaction_status(self):
  1533. project = self.create_project()
  1534. data = load_data("transaction", timestamp=before_now(minutes=1))
  1535. data["transaction"] = "/transactionstatus/1"
  1536. self.store_event(data, project_id=project.id)
  1537. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1538. query = {
  1539. "field": ["event.type", "count(id)"],
  1540. "query": "event.type:transaction !has:transaction.status",
  1541. "sort": "-count(id)",
  1542. "statsPeriod": "24h",
  1543. }
  1544. response = self.do_request(query, features=features)
  1545. assert response.status_code == 200, response.content
  1546. data = response.data["data"]
  1547. assert len(data) == 1
  1548. assert data[0]["count_id"] == 0
  1549. def test_tag_that_looks_like_aggregation(self):
  1550. project = self.create_project()
  1551. data = {
  1552. "message": "Failure state",
  1553. "timestamp": self.two_min_ago,
  1554. "tags": {"count_diff": 99},
  1555. }
  1556. self.store_event(data, project_id=project.id)
  1557. query = {
  1558. "field": ["message", "count_diff", "count()"],
  1559. "query": "",
  1560. "project": [project.id],
  1561. "statsPeriod": "24h",
  1562. }
  1563. response = self.do_request(query)
  1564. assert response.status_code == 200, response.content
  1565. meta = response.data["meta"]
  1566. assert "string" == meta["count_diff"], "tags should not be counted as integers"
  1567. assert "string" == meta["message"]
  1568. assert "integer" == meta["count"]
  1569. assert 1 == len(response.data["data"])
  1570. data = response.data["data"][0]
  1571. assert "99" == data["count_diff"]
  1572. assert "Failure state" == data["message"]
  1573. assert 1 == data["count"]
  1574. def test_aggregate_negation(self):
  1575. project = self.create_project()
  1576. data = load_data(
  1577. "transaction",
  1578. timestamp=before_now(minutes=1),
  1579. start_timestamp=before_now(minutes=1, seconds=5),
  1580. )
  1581. self.store_event(data, project_id=project.id)
  1582. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1583. query = {
  1584. "field": ["event.type", "p99()"],
  1585. "query": "event.type:transaction p99():5s",
  1586. "statsPeriod": "24h",
  1587. }
  1588. response = self.do_request(query, features=features)
  1589. assert response.status_code == 200, response.content
  1590. data = response.data["data"]
  1591. assert len(data) == 1
  1592. query = {
  1593. "field": ["event.type", "p99()"],
  1594. "query": "event.type:transaction !p99():5s",
  1595. "statsPeriod": "24h",
  1596. }
  1597. response = self.do_request(query, features=features)
  1598. assert response.status_code == 200, response.content
  1599. data = response.data["data"]
  1600. assert len(data) == 0
  1601. def test_all_aggregates_in_columns(self):
  1602. project = self.create_project()
  1603. data = load_data(
  1604. "transaction",
  1605. timestamp=before_now(minutes=2),
  1606. start_timestamp=before_now(minutes=2, seconds=5),
  1607. )
  1608. data["transaction"] = "/failure_rate/1"
  1609. self.store_event(data, project_id=project.id)
  1610. data = load_data(
  1611. "transaction",
  1612. timestamp=before_now(minutes=1),
  1613. start_timestamp=before_now(minutes=1, seconds=5),
  1614. )
  1615. data["transaction"] = "/failure_rate/1"
  1616. data["contexts"]["trace"]["status"] = "unauthenticated"
  1617. event = self.store_event(data, project_id=project.id)
  1618. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1619. query = {
  1620. "field": [
  1621. "event.type",
  1622. "p50()",
  1623. "p75()",
  1624. "p95()",
  1625. "p99()",
  1626. "p100()",
  1627. "percentile(transaction.duration, 0.99)",
  1628. "apdex(300)",
  1629. "user_misery(300)",
  1630. "failure_rate()",
  1631. ],
  1632. "query": "event.type:transaction",
  1633. }
  1634. response = self.do_request(query, features=features)
  1635. assert response.status_code == 200, response.content
  1636. meta = response.data["meta"]
  1637. assert meta["p50"] == "duration"
  1638. assert meta["p75"] == "duration"
  1639. assert meta["p95"] == "duration"
  1640. assert meta["p99"] == "duration"
  1641. assert meta["p100"] == "duration"
  1642. assert meta["percentile_transaction_duration_0_99"] == "duration"
  1643. assert meta["apdex_300"] == "number"
  1644. assert meta["failure_rate"] == "percentage"
  1645. assert meta["user_misery_300"] == "number"
  1646. data = response.data["data"]
  1647. assert len(data) == 1
  1648. assert data[0]["p50"] == 5000
  1649. assert data[0]["p75"] == 5000
  1650. assert data[0]["p95"] == 5000
  1651. assert data[0]["p99"] == 5000
  1652. assert data[0]["p100"] == 5000
  1653. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1654. assert data[0]["apdex_300"] == 0.0
  1655. assert data[0]["user_misery_300"] == 1
  1656. assert data[0]["failure_rate"] == 0.5
  1657. query = {
  1658. "field": ["event.type", "last_seen()", "latest_event()"],
  1659. "query": "event.type:transaction",
  1660. }
  1661. response = self.do_request(query, features=features)
  1662. assert response.status_code == 200, response.content
  1663. data = response.data["data"]
  1664. assert len(data) == 1
  1665. assert iso_format(before_now(minutes=1))[:-5] in data[0]["last_seen"]
  1666. assert data[0]["latest_event"] == event.event_id
  1667. query = {
  1668. "field": [
  1669. "event.type",
  1670. "count()",
  1671. "count(id)",
  1672. "count_unique(project)",
  1673. "min(transaction.duration)",
  1674. "max(transaction.duration)",
  1675. "avg(transaction.duration)",
  1676. "sum(transaction.duration)",
  1677. ],
  1678. "query": "event.type:transaction",
  1679. }
  1680. response = self.do_request(query, features=features)
  1681. assert response.status_code == 200, response.content
  1682. data = response.data["data"]
  1683. assert len(data) == 1
  1684. assert data[0]["count"] == 2
  1685. assert data[0]["count_id"] == 2
  1686. assert data[0]["count_unique_project"] == 1
  1687. assert data[0]["min_transaction_duration"] == 5000
  1688. assert data[0]["max_transaction_duration"] == 5000
  1689. assert data[0]["avg_transaction_duration"] == 5000
  1690. assert data[0]["sum_transaction_duration"] == 10000
  1691. def test_all_aggregates_in_query(self):
  1692. project = self.create_project()
  1693. data = load_data(
  1694. "transaction",
  1695. timestamp=before_now(minutes=2),
  1696. start_timestamp=before_now(minutes=2, seconds=5),
  1697. )
  1698. data["transaction"] = "/failure_rate/1"
  1699. self.store_event(data, project_id=project.id)
  1700. data = load_data(
  1701. "transaction",
  1702. timestamp=before_now(minutes=1),
  1703. start_timestamp=before_now(minutes=1, seconds=5),
  1704. )
  1705. data["transaction"] = "/failure_rate/2"
  1706. data["contexts"]["trace"]["status"] = "unauthenticated"
  1707. self.store_event(data, project_id=project.id)
  1708. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1709. query = {
  1710. "field": [
  1711. "event.type",
  1712. "p50()",
  1713. "p75()",
  1714. "p95()",
  1715. "percentile(transaction.duration, 0.99)",
  1716. "p100()",
  1717. ],
  1718. "query": "event.type:transaction p50():>100 p75():>1000 p95():>1000 p100():>1000 percentile(transaction.duration, 0.99):>1000",
  1719. }
  1720. response = self.do_request(query, features=features)
  1721. assert response.status_code == 200, response.content
  1722. data = response.data["data"]
  1723. assert len(data) == 1
  1724. assert data[0]["p50"] == 5000
  1725. assert data[0]["p75"] == 5000
  1726. assert data[0]["p95"] == 5000
  1727. assert data[0]["p100"] == 5000
  1728. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1729. query = {
  1730. "field": ["event.type", "apdex(300)", "user_misery(300)", "failure_rate()"],
  1731. "query": "event.type:transaction apdex(300):>-1.0 failure_rate():>0.25",
  1732. }
  1733. response = self.do_request(query, features=features)
  1734. assert response.status_code == 200, response.content
  1735. data = response.data["data"]
  1736. assert len(data) == 1
  1737. assert data[0]["apdex_300"] == 0.0
  1738. assert data[0]["user_misery_300"] == 1
  1739. assert data[0]["failure_rate"] == 0.5
  1740. query = {
  1741. "field": ["event.type", "last_seen()", "latest_event()"],
  1742. "query": u"event.type:transaction last_seen():>1990-12-01T00:00:00",
  1743. }
  1744. response = self.do_request(query, features=features)
  1745. assert response.status_code == 200, response.content
  1746. data = response.data["data"]
  1747. assert len(data) == 1
  1748. query = {
  1749. "field": ["event.type", "count()", "count(id)", "count_unique(transaction)"],
  1750. "query": "event.type:transaction count():>1 count(id):>1 count_unique(transaction):>1",
  1751. }
  1752. response = self.do_request(query, features=features)
  1753. assert response.status_code == 200, response.content
  1754. data = response.data["data"]
  1755. assert len(data) == 1
  1756. assert data[0]["count"] == 2
  1757. assert data[0]["count_id"] == 2
  1758. assert data[0]["count_unique_transaction"] == 2
  1759. query = {
  1760. "field": [
  1761. "event.type",
  1762. "min(transaction.duration)",
  1763. "max(transaction.duration)",
  1764. "avg(transaction.duration)",
  1765. "sum(transaction.duration)",
  1766. ],
  1767. "query": "event.type:transaction min(transaction.duration):>1000 max(transaction.duration):>1000 avg(transaction.duration):>1000 sum(transaction.duration):>1000",
  1768. }
  1769. response = self.do_request(query, features=features)
  1770. assert response.status_code == 200, response.content
  1771. data = response.data["data"]
  1772. assert len(data) == 1
  1773. assert data[0]["min_transaction_duration"] == 5000
  1774. assert data[0]["max_transaction_duration"] == 5000
  1775. assert data[0]["avg_transaction_duration"] == 5000
  1776. assert data[0]["sum_transaction_duration"] == 10000
  1777. query = {
  1778. "field": ["event.type", "apdex(400)"],
  1779. "query": "event.type:transaction apdex(400):0",
  1780. }
  1781. response = self.do_request(query, features=features)
  1782. assert response.status_code == 200, response.content
  1783. data = response.data["data"]
  1784. assert len(data) == 1
  1785. assert data[0]["apdex_400"] == 0
  1786. def test_functions_in_orderby(self):
  1787. project = self.create_project()
  1788. data = load_data(
  1789. "transaction",
  1790. timestamp=before_now(minutes=2),
  1791. start_timestamp=before_now(minutes=2, seconds=5),
  1792. )
  1793. data["transaction"] = "/failure_rate/1"
  1794. self.store_event(data, project_id=project.id)
  1795. data = load_data(
  1796. "transaction",
  1797. timestamp=before_now(minutes=1),
  1798. start_timestamp=before_now(minutes=1, seconds=5),
  1799. )
  1800. data["transaction"] = "/failure_rate/2"
  1801. data["contexts"]["trace"]["status"] = "unauthenticated"
  1802. event = self.store_event(data, project_id=project.id)
  1803. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1804. query = {
  1805. "field": ["event.type", "p75()"],
  1806. "sort": "-p75",
  1807. "query": "event.type:transaction",
  1808. }
  1809. response = self.do_request(query, features=features)
  1810. assert response.status_code == 200, response.content
  1811. data = response.data["data"]
  1812. assert len(data) == 1
  1813. assert data[0]["p75"] == 5000
  1814. query = {
  1815. "field": ["event.type", "percentile(transaction.duration, 0.99)"],
  1816. "sort": "-percentile_transaction_duration_0_99",
  1817. "query": "event.type:transaction",
  1818. }
  1819. response = self.do_request(query, features=features)
  1820. assert response.status_code == 200, response.content
  1821. data = response.data["data"]
  1822. assert len(data) == 1
  1823. assert data[0]["percentile_transaction_duration_0_99"] == 5000
  1824. query = {
  1825. "field": ["event.type", "apdex(300)"],
  1826. "sort": "-apdex(300)",
  1827. "query": "event.type:transaction",
  1828. }
  1829. response = self.do_request(query, features=features)
  1830. assert response.status_code == 200, response.content
  1831. data = response.data["data"]
  1832. assert len(data) == 1
  1833. assert data[0]["apdex_300"] == 0.0
  1834. query = {
  1835. "field": ["event.type", "latest_event()"],
  1836. "query": u"event.type:transaction",
  1837. "sort": "latest_event",
  1838. }
  1839. response = self.do_request(query, features=features)
  1840. assert response.status_code == 200, response.content
  1841. data = response.data["data"]
  1842. assert len(data) == 1
  1843. assert data[0]["latest_event"] == event.event_id
  1844. query = {
  1845. "field": ["event.type", "count_unique(transaction)"],
  1846. "query": "event.type:transaction",
  1847. "sort": "-count_unique_transaction",
  1848. }
  1849. response = self.do_request(query, features=features)
  1850. assert response.status_code == 200, response.content
  1851. data = response.data["data"]
  1852. assert len(data) == 1
  1853. assert data[0]["count_unique_transaction"] == 2
  1854. query = {
  1855. "field": ["event.type", "min(transaction.duration)"],
  1856. "query": "event.type:transaction",
  1857. "sort": "-min_transaction_duration",
  1858. }
  1859. response = self.do_request(query, features=features)
  1860. assert response.status_code == 200, response.content
  1861. data = response.data["data"]
  1862. assert len(data) == 1
  1863. assert data[0]["min_transaction_duration"] == 5000
  1864. def test_issue_alias_in_aggregate(self):
  1865. project = self.create_project()
  1866. self.store_event(
  1867. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1868. project_id=project.id,
  1869. )
  1870. self.store_event(
  1871. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  1872. project_id=project.id,
  1873. )
  1874. query = {
  1875. "field": ["event.type", "count_unique(issue)"],
  1876. "query": "count_unique(issue):>1",
  1877. }
  1878. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1879. response = self.do_request(query, features=features)
  1880. assert response.status_code == 200, response.content
  1881. data = response.data["data"]
  1882. assert len(data) == 1
  1883. assert data[0]["count_unique_issue"] == 2
  1884. def test_deleted_issue_in_results(self):
  1885. project = self.create_project()
  1886. event1 = self.store_event(
  1887. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1888. project_id=project.id,
  1889. )
  1890. event2 = self.store_event(
  1891. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  1892. project_id=project.id,
  1893. )
  1894. event2.group.delete()
  1895. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1896. query = {"field": ["issue", "count()"], "sort": "issue"}
  1897. response = self.do_request(query, features=features)
  1898. assert response.status_code == 200, response.content
  1899. data = response.data["data"]
  1900. assert len(data) == 2
  1901. assert data[0]["issue"] == event1.group.qualified_short_id
  1902. assert data[1]["issue"] == "unknown"
  1903. def test_last_seen_negative_duration(self):
  1904. project = self.create_project()
  1905. self.store_event(
  1906. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1907. project_id=project.id,
  1908. )
  1909. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1910. query = {"field": ["id", "last_seen()"], "query": "last_seen():-30d"}
  1911. response = self.do_request(query, features=features)
  1912. assert response.status_code == 200, response.content
  1913. data = response.data["data"]
  1914. assert len(data) == 1
  1915. assert data[0]["id"] == "f" * 32
  1916. def test_last_seen_aggregate_condition(self):
  1917. project = self.create_project()
  1918. self.store_event(
  1919. data={"event_id": "f" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  1920. project_id=project.id,
  1921. )
  1922. query = {
  1923. "field": ["id", "last_seen()"],
  1924. "query": "last_seen():>{}".format(iso_format(before_now(days=30))),
  1925. }
  1926. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1927. response = self.do_request(query, features=features)
  1928. assert response.status_code == 200, response.content
  1929. data = response.data["data"]
  1930. assert len(data) == 1
  1931. assert data[0]["id"] == "f" * 32
  1932. def test_conditional_filter(self):
  1933. project = self.create_project()
  1934. for v in ["a", "b"]:
  1935. self.store_event(
  1936. data={
  1937. "event_id": v * 32,
  1938. "timestamp": self.two_min_ago,
  1939. "fingerprint": ["group_1"],
  1940. },
  1941. project_id=project.id,
  1942. )
  1943. query = {
  1944. "field": ["id"],
  1945. "query": "id:{} OR id:{}".format("a" * 32, "b" * 32),
  1946. "orderby": "id",
  1947. }
  1948. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  1949. response = self.do_request(query, features=features)
  1950. assert response.status_code == 200, response.content
  1951. data = response.data["data"]
  1952. assert len(data) == 2
  1953. assert data[0]["id"] == "a" * 32
  1954. assert data[1]["id"] == "b" * 32
  1955. def test_aggregation_comparison_with_conditional_filter(self):
  1956. project = self.create_project()
  1957. self.store_event(
  1958. data={
  1959. "event_id": "a" * 32,
  1960. "timestamp": self.min_ago,
  1961. "fingerprint": ["group_1"],
  1962. "user": {"email": "foo@example.com"},
  1963. "environment": "prod",
  1964. },
  1965. project_id=project.id,
  1966. )
  1967. self.store_event(
  1968. data={
  1969. "event_id": "b" * 32,
  1970. "timestamp": self.min_ago,
  1971. "fingerprint": ["group_2"],
  1972. "user": {"email": "foo@example.com"},
  1973. "environment": "staging",
  1974. },
  1975. project_id=project.id,
  1976. )
  1977. event = self.store_event(
  1978. data={
  1979. "event_id": "c" * 32,
  1980. "timestamp": self.min_ago,
  1981. "fingerprint": ["group_2"],
  1982. "user": {"email": "foo@example.com"},
  1983. "environment": "prod",
  1984. },
  1985. project_id=project.id,
  1986. )
  1987. self.store_event(
  1988. data={
  1989. "event_id": "d" * 32,
  1990. "timestamp": self.min_ago,
  1991. "fingerprint": ["group_2"],
  1992. "user": {"email": "foo@example.com"},
  1993. "environment": "canary",
  1994. },
  1995. project_id=project.id,
  1996. )
  1997. query = {
  1998. "field": ["issue.id", "count(id)"],
  1999. "query": "count(id):>1 user.email:foo@example.com AND (environment:prod OR environment:staging)",
  2000. "orderby": "issue.id",
  2001. }
  2002. response = self.do_request(query)
  2003. assert response.status_code == 200, response.content
  2004. assert len(response.data["data"]) == 1
  2005. data = response.data["data"]
  2006. assert data[0]["issue.id"] == event.group_id
  2007. assert data[0]["count_id"] == 2
  2008. def test_messed_up_function_values(self):
  2009. # TODO (evanh): It would be nice if this surfaced an error to the user.
  2010. # The problem: The && causes the parser to treat that term not as a bad
  2011. # function call but a valid raw search with parens in it. It's not trivial
  2012. # to change the parser to recognize "bad function values" and surface them.
  2013. project = self.create_project()
  2014. for v in ["a", "b"]:
  2015. self.store_event(
  2016. data={
  2017. "event_id": v * 32,
  2018. "timestamp": self.two_min_ago,
  2019. "fingerprint": ["group_1"],
  2020. },
  2021. project_id=project.id,
  2022. )
  2023. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2024. query = {
  2025. "field": [
  2026. "transaction",
  2027. "project",
  2028. "epm()",
  2029. "p50()",
  2030. "p95()",
  2031. "failure_rate()",
  2032. "apdex(300)",
  2033. "count_unique(user)",
  2034. "user_misery(300)",
  2035. ],
  2036. "query": "failure_rate():>0.003&& users:>10 event.type:transaction",
  2037. "sort": "-failure_rate",
  2038. "statsPeriod": "24h",
  2039. }
  2040. response = self.do_request(query, features=features)
  2041. assert response.status_code == 200, response.content
  2042. data = response.data["data"]
  2043. assert len(data) == 0
  2044. def test_context_fields_between_datasets(self):
  2045. project = self.create_project()
  2046. event_data = load_data("android")
  2047. transaction_data = load_data("transaction")
  2048. event_data["spans"] = transaction_data["spans"]
  2049. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  2050. event_data["type"] = "transaction"
  2051. event_data["transaction"] = "/failure_rate/1"
  2052. event_data["timestamp"] = iso_format(before_now(minutes=1))
  2053. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  2054. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  2055. self.store_event(event_data, project_id=project.id)
  2056. event_data["type"] = "error"
  2057. self.store_event(event_data, project_id=project.id)
  2058. fields = [
  2059. "os.build",
  2060. "os.kernel_version",
  2061. "device.arch",
  2062. # TODO: battery level is not consistent across both datasets
  2063. # "device.battery_level",
  2064. "device.brand",
  2065. "device.charging",
  2066. "device.locale",
  2067. "device.model_id",
  2068. "device.name",
  2069. "device.online",
  2070. "device.orientation",
  2071. "device.simulator",
  2072. "device.uuid",
  2073. ]
  2074. data = [
  2075. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  2076. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  2077. ]
  2078. for datum in data:
  2079. response = self.do_request(datum)
  2080. assert response.status_code == 200, response.content
  2081. assert len(response.data["data"]) == 1, datum
  2082. results = response.data["data"]
  2083. assert results[0]["count"] == 1, datum
  2084. for field in fields:
  2085. key, value = field.split(".", 1)
  2086. expected = six.text_type(event_data["contexts"][key][value])
  2087. assert results[0][field] == expected, field + six.text_type(datum)
  2088. def test_http_fields_between_datasets(self):
  2089. project = self.create_project()
  2090. event_data = load_data("android")
  2091. transaction_data = load_data("transaction")
  2092. event_data["spans"] = transaction_data["spans"]
  2093. event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
  2094. event_data["type"] = "transaction"
  2095. event_data["transaction"] = "/failure_rate/1"
  2096. event_data["timestamp"] = iso_format(before_now(minutes=1))
  2097. event_data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  2098. event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
  2099. event_data["request"] = transaction_data["request"]
  2100. self.store_event(event_data, project_id=project.id)
  2101. event_data["type"] = "error"
  2102. self.store_event(event_data, project_id=project.id)
  2103. fields = [
  2104. "http.method",
  2105. "http.referer",
  2106. "http.url",
  2107. ]
  2108. expected = [
  2109. "GET",
  2110. "fixtures.transaction",
  2111. "http://countries:8010/country_by_code/",
  2112. ]
  2113. data = [
  2114. {"field": fields + ["location", "count()"], "query": "event.type:error"},
  2115. {"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
  2116. ]
  2117. for datum in data:
  2118. response = self.do_request(datum)
  2119. assert response.status_code == 200, response.content
  2120. assert len(response.data["data"]) == 1, datum
  2121. results = response.data["data"]
  2122. assert results[0]["count"] == 1, datum
  2123. for (field, exp) in zip(fields, expected):
  2124. assert results[0][field] == exp, field + six.text_type(datum)
  2125. def test_histogram_function(self):
  2126. project = self.create_project()
  2127. start = before_now(minutes=2).replace(microsecond=0)
  2128. latencies = [
  2129. (1, 500, 5),
  2130. (1000, 1500, 4),
  2131. (3000, 3500, 3),
  2132. (6000, 6500, 2),
  2133. (10000, 10000, 1), # just to make the math easy
  2134. ]
  2135. values = []
  2136. for bucket in latencies:
  2137. for i in range(bucket[2]):
  2138. # Don't generate a wide range of variance as the buckets can mis-align.
  2139. milliseconds = random.randint(bucket[0], bucket[1])
  2140. values.append(milliseconds)
  2141. data = load_data("transaction")
  2142. data["transaction"] = "/failure_rate/{}".format(milliseconds)
  2143. data["timestamp"] = iso_format(start)
  2144. data["start_timestamp"] = (start - timedelta(milliseconds=milliseconds)).isoformat()
  2145. self.store_event(data, project_id=project.id)
  2146. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2147. query = {
  2148. "field": ["histogram(transaction.duration, 10)", "count()"],
  2149. "query": "event.type:transaction",
  2150. "sort": "histogram_transaction_duration_10",
  2151. }
  2152. response = self.do_request(query, features=features)
  2153. assert response.status_code == 200, response.content
  2154. data = response.data["data"]
  2155. assert len(data) == 11
  2156. bucket_size = ceil((max(values) - min(values)) / 10.0)
  2157. expected = [
  2158. (0, 5),
  2159. (bucket_size, 4),
  2160. (bucket_size * 2, 0),
  2161. (bucket_size * 3, 3),
  2162. (bucket_size * 4, 0),
  2163. (bucket_size * 5, 0),
  2164. (bucket_size * 6, 2),
  2165. (bucket_size * 7, 0),
  2166. (bucket_size * 8, 0),
  2167. (bucket_size * 9, 0),
  2168. (bucket_size * 10, 1),
  2169. ]
  2170. for idx, datum in enumerate(data):
  2171. assert datum["histogram_transaction_duration_10"] == expected[idx][0]
  2172. assert datum["count"] == expected[idx][1]
  2173. def test_histogram_function_with_filters(self):
  2174. project = self.create_project()
  2175. start = before_now(minutes=2).replace(microsecond=0)
  2176. latencies = [
  2177. (1, 500, 5),
  2178. (1000, 1500, 4),
  2179. (3000, 3500, 3),
  2180. (6000, 6500, 2),
  2181. (10000, 10000, 1), # just to make the math easy
  2182. ]
  2183. values = []
  2184. for bucket in latencies:
  2185. for i in range(bucket[2]):
  2186. milliseconds = random.randint(bucket[0], bucket[1])
  2187. values.append(milliseconds)
  2188. data = load_data("transaction")
  2189. data["transaction"] = "/failure_rate/sleepy_gary/{}".format(milliseconds)
  2190. data["timestamp"] = iso_format(start)
  2191. data["start_timestamp"] = (start - timedelta(milliseconds=milliseconds)).isoformat()
  2192. self.store_event(data, project_id=project.id)
  2193. # Add a transaction that totally throws off the buckets
  2194. milliseconds = random.randint(bucket[0], bucket[1])
  2195. data = load_data("transaction")
  2196. data["transaction"] = "/failure_rate/hamurai"
  2197. data["timestamp"] = iso_format(start)
  2198. data["start_timestamp"] = iso_format(start - timedelta(milliseconds=1000000))
  2199. self.store_event(data, project_id=project.id)
  2200. query = {
  2201. "field": ["histogram(transaction.duration, 10)", "count()"],
  2202. "query": "event.type:transaction transaction:/failure_rate/sleepy_gary*",
  2203. "sort": "histogram_transaction_duration_10",
  2204. }
  2205. features = {"organizations:discover-basic": True, "organizations:global-views": True}
  2206. response = self.do_request(query, features=features)
  2207. assert response.status_code == 200, response.content
  2208. data = response.data["data"]
  2209. assert len(data) == 11
  2210. bucket_size = ceil((max(values) - min(values)) / 10.0)
  2211. expected = [
  2212. (0, 5),
  2213. (bucket_size, 4),
  2214. (bucket_size * 2, 0),
  2215. (bucket_size * 3, 3),
  2216. (bucket_size * 4, 0),
  2217. (bucket_size * 5, 0),
  2218. (bucket_size * 6, 2),
  2219. (bucket_size * 7, 0),
  2220. (bucket_size * 8, 0),
  2221. (bucket_size * 9, 0),
  2222. (bucket_size * 10, 1),
  2223. ]
  2224. for idx, datum in enumerate(data):
  2225. assert datum["histogram_transaction_duration_10"] == expected[idx][0]
  2226. assert datum["count"] == expected[idx][1]
  2227. @mock.patch("sentry.utils.snuba.quantize_time")
  2228. def test_quantize_dates(self, mock_quantize):
  2229. self.create_project()
  2230. mock_quantize.return_value = before_now(days=1).replace(tzinfo=utc)
  2231. # Don't quantize short time periods
  2232. query = {"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]}
  2233. self.do_request(query)
  2234. # Don't quantize absolute date periods
  2235. self.do_request(query)
  2236. query = {
  2237. "start": iso_format(before_now(days=20)),
  2238. "end": iso_format(before_now(days=15)),
  2239. "query": "",
  2240. "field": ["id", "timestamp"],
  2241. }
  2242. self.do_request(query)
  2243. assert len(mock_quantize.mock_calls) == 0
  2244. # Quantize long date periods
  2245. query = {"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""}
  2246. self.do_request(query)
  2247. assert len(mock_quantize.mock_calls) == 2
  2248. def test_limit_number_of_fields(self):
  2249. self.create_project()
  2250. for i in range(1, 25):
  2251. response = self.do_request({"field": ["id"] * i})
  2252. if i <= 20:
  2253. assert response.status_code == 200
  2254. else:
  2255. assert response.status_code == 400
  2256. assert (
  2257. response.data["detail"]
  2258. == "You can view up to 20 fields at a time. Please delete some and try again."
  2259. )