test_discover_saved_queries.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. from django.urls import reverse
  2. from sentry.discover.models import DiscoverSavedQuery
  3. from sentry.testutils import APITestCase, SnubaTestCase
  4. from sentry.testutils.helpers.datetime import before_now, iso_format
  5. class DiscoverSavedQueryBase(APITestCase, SnubaTestCase):
  6. def setUp(self):
  7. super().setUp()
  8. self.login_as(user=self.user)
  9. self.org = self.create_organization(owner=self.user)
  10. self.projects = [
  11. self.create_project(organization=self.org),
  12. self.create_project(organization=self.org),
  13. ]
  14. self.project_ids = [project.id for project in self.projects]
  15. self.project_ids_without_access = [self.create_project().id]
  16. query = {"fields": ["test"], "conditions": [], "limit": 10}
  17. model = DiscoverSavedQuery.objects.create(
  18. organization=self.org, created_by=self.user, name="Test query", query=query, version=1
  19. )
  20. model.set_projects(self.project_ids)
  21. class DiscoverSavedQueriesTest(DiscoverSavedQueryBase):
  22. feature_name = "organizations:discover"
  23. def setUp(self):
  24. super().setUp()
  25. self.url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  26. def test_get(self):
  27. with self.feature(self.feature_name):
  28. response = self.client.get(self.url)
  29. assert response.status_code == 200, response.content
  30. assert len(response.data) == 1
  31. assert response.data[0]["name"] == "Test query"
  32. assert response.data[0]["projects"] == self.project_ids
  33. assert response.data[0]["fields"] == ["test"]
  34. assert response.data[0]["conditions"] == []
  35. assert response.data[0]["limit"] == 10
  36. assert response.data[0]["version"] == 1
  37. assert "createdBy" in response.data[0]
  38. assert response.data[0]["createdBy"]["username"] == self.user.username
  39. assert not response.data[0]["expired"]
  40. def test_get_version_filter(self):
  41. with self.feature(self.feature_name):
  42. response = self.client.get(self.url, format="json", data={"query": "version:1"})
  43. assert response.status_code == 200, response.content
  44. assert len(response.data) == 1
  45. assert response.data[0]["name"] == "Test query"
  46. with self.feature(self.feature_name):
  47. response = self.client.get(self.url, format="json", data={"query": "version:2"})
  48. assert response.status_code == 200, response.content
  49. assert len(response.data) == 0
  50. def test_get_name_filter(self):
  51. with self.feature(self.feature_name):
  52. response = self.client.get(self.url, format="json", data={"query": "Test"})
  53. assert response.status_code == 200, response.content
  54. assert len(response.data) == 1
  55. assert response.data[0]["name"] == "Test query"
  56. with self.feature(self.feature_name):
  57. # Also available as the name: filter.
  58. response = self.client.get(self.url, format="json", data={"query": "name:Test"})
  59. assert response.status_code == 200, response.content
  60. assert len(response.data) == 1
  61. assert response.data[0]["name"] == "Test query"
  62. with self.feature(self.feature_name):
  63. response = self.client.get(self.url, format="json", data={"query": "name:Nope"})
  64. assert response.status_code == 200, response.content
  65. assert len(response.data) == 0
  66. def test_get_all_paginated(self):
  67. for i in range(0, 10):
  68. query = {"fields": ["test"], "conditions": [], "limit": 10}
  69. model = DiscoverSavedQuery.objects.create(
  70. organization=self.org,
  71. created_by=self.user,
  72. name=f"My query {i}",
  73. query=query,
  74. version=1,
  75. )
  76. model.set_projects(self.project_ids)
  77. with self.feature(self.feature_name):
  78. response = self.client.get(self.url, data={"per_page": 1})
  79. assert response.status_code == 200, response.content
  80. assert len(response.data) == 1
  81. with self.feature(self.feature_name):
  82. # The all parameter ignores pagination and returns all values.
  83. response = self.client.get(self.url, data={"per_page": 1, "all": 1})
  84. assert response.status_code == 200, response.content
  85. assert len(response.data) == 11
  86. def test_get_sortby(self):
  87. query = {"fields": ["message"], "query": "", "limit": 10}
  88. model = DiscoverSavedQuery.objects.create(
  89. organization=self.org,
  90. created_by=self.user,
  91. name="My query",
  92. query=query,
  93. version=2,
  94. date_created=before_now(minutes=10),
  95. date_updated=before_now(minutes=10),
  96. )
  97. model.set_projects(self.project_ids)
  98. sort_options = {
  99. "dateCreated": True,
  100. "-dateCreated": False,
  101. "dateUpdated": True,
  102. "-dateUpdated": False,
  103. "name": True,
  104. "-name": False,
  105. }
  106. for sorting, forward_sort in sort_options.items():
  107. with self.feature(self.feature_name):
  108. response = self.client.get(self.url, data={"sortBy": sorting})
  109. assert response.status_code == 200
  110. values = [row[sorting.strip("-")] for row in response.data]
  111. if not forward_sort:
  112. values = list(reversed(values))
  113. assert list(sorted(values)) == values
  114. def test_get_sortby_most_popular(self):
  115. query = {"fields": ["message"], "query": "", "limit": 10}
  116. model = DiscoverSavedQuery.objects.create(
  117. organization=self.org,
  118. created_by=self.user,
  119. name="My query",
  120. query=query,
  121. version=2,
  122. visits=3,
  123. date_created=before_now(minutes=10),
  124. date_updated=before_now(minutes=10),
  125. last_visited=before_now(minutes=5),
  126. )
  127. model.set_projects(self.project_ids)
  128. for forward_sort in [True, False]:
  129. sorting = "mostPopular" if forward_sort else "-mostPopular"
  130. with self.feature(self.feature_name):
  131. response = self.client.get(self.url, data={"sortBy": sorting})
  132. assert response.status_code == 200
  133. values = [row["name"] for row in response.data]
  134. expected = ["My query", "Test query"]
  135. if not forward_sort:
  136. expected = list(reversed(expected))
  137. assert values == expected
  138. def test_get_sortby_recently_viewed(self):
  139. query = {"fields": ["message"], "query": "", "limit": 10}
  140. model = DiscoverSavedQuery.objects.create(
  141. organization=self.org,
  142. created_by=self.user,
  143. name="My query",
  144. query=query,
  145. version=2,
  146. visits=3,
  147. date_created=before_now(minutes=10),
  148. date_updated=before_now(minutes=10),
  149. last_visited=before_now(minutes=5),
  150. )
  151. model.set_projects(self.project_ids)
  152. for forward_sort in [True, False]:
  153. sorting = "recentlyViewed" if forward_sort else "-recentlyViewed"
  154. with self.feature(self.feature_name):
  155. response = self.client.get(self.url, data={"sortBy": sorting})
  156. assert response.status_code == 200
  157. values = [row["name"] for row in response.data]
  158. expected = ["Test query", "My query"]
  159. if not forward_sort:
  160. expected = list(reversed(expected))
  161. assert values == expected
  162. def test_get_sortby_myqueries(self):
  163. uhoh_user = self.create_user(username="uhoh")
  164. self.create_member(organization=self.org, user=uhoh_user)
  165. whoops_user = self.create_user(username="whoops")
  166. self.create_member(organization=self.org, user=whoops_user)
  167. query = {"fields": ["message"], "query": "", "limit": 10}
  168. model = DiscoverSavedQuery.objects.create(
  169. organization=self.org,
  170. created_by=uhoh_user,
  171. name="a query for uhoh",
  172. query=query,
  173. version=2,
  174. date_created=before_now(minutes=10),
  175. date_updated=before_now(minutes=10),
  176. )
  177. model.set_projects(self.project_ids)
  178. model = DiscoverSavedQuery.objects.create(
  179. organization=self.org,
  180. created_by=whoops_user,
  181. name="a query for whoops",
  182. query=query,
  183. version=2,
  184. date_created=before_now(minutes=10),
  185. date_updated=before_now(minutes=10),
  186. )
  187. model.set_projects(self.project_ids)
  188. with self.feature(self.feature_name):
  189. response = self.client.get(self.url, data={"sortBy": "myqueries"})
  190. assert response.status_code == 200, response.content
  191. values = [int(item["createdBy"]["id"]) for item in response.data]
  192. assert values == [self.user.id, uhoh_user.id, whoops_user.id]
  193. def test_get_expired_query(self):
  194. query = {
  195. "start": iso_format(before_now(days=90)),
  196. "end": iso_format(before_now(days=61)),
  197. }
  198. DiscoverSavedQuery.objects.create(
  199. organization=self.org,
  200. created_by=self.user,
  201. name="My expired query",
  202. query=query,
  203. version=2,
  204. date_created=before_now(days=90),
  205. date_updated=before_now(minutes=10),
  206. )
  207. with self.options({"system.event-retention-days": 60}), self.feature(self.feature_name):
  208. response = self.client.get(self.url, {"query": "name:My expired query"})
  209. assert response.status_code == 200, response.content
  210. assert response.data[0]["expired"]
  211. def test_get_ignores_homepage_queries(self):
  212. query = {"fields": ["test"], "conditions": [], "limit": 10}
  213. model = DiscoverSavedQuery.objects.create(
  214. organization=self.org,
  215. created_by=self.user,
  216. name="Homepage Test Query",
  217. query=query,
  218. version=2,
  219. date_created=before_now(minutes=10),
  220. date_updated=before_now(minutes=10),
  221. is_homepage=True,
  222. )
  223. model.set_projects(self.project_ids)
  224. with self.feature(self.feature_name):
  225. response = self.client.get(self.url)
  226. assert response.status_code == 200, response.content
  227. assert len(response.data) == 1
  228. assert not any([query["name"] == "Homepage Test Query" for query in response.data])
  229. def test_post(self):
  230. with self.feature(self.feature_name):
  231. response = self.client.post(
  232. self.url,
  233. {
  234. "name": "New query",
  235. "projects": self.project_ids,
  236. "fields": [],
  237. "range": "24h",
  238. "limit": 20,
  239. "conditions": [],
  240. "aggregations": [],
  241. "orderby": "-time",
  242. },
  243. )
  244. assert response.status_code == 201, response.content
  245. assert response.data["name"] == "New query"
  246. assert response.data["projects"] == self.project_ids
  247. assert response.data["range"] == "24h"
  248. assert not hasattr(response.data, "start")
  249. assert not hasattr(response.data, "end")
  250. def test_post_invalid_projects(self):
  251. with self.feature(self.feature_name):
  252. response = self.client.post(
  253. self.url,
  254. {
  255. "name": "New query",
  256. "projects": self.project_ids_without_access,
  257. "fields": [],
  258. "range": "24h",
  259. "limit": 20,
  260. "conditions": [],
  261. "aggregations": [],
  262. "orderby": "-time",
  263. },
  264. )
  265. assert response.status_code == 403, response.content
  266. def test_post_all_projects(self):
  267. with self.feature(self.feature_name):
  268. response = self.client.post(
  269. self.url,
  270. {
  271. "name": "All projects",
  272. "projects": [-1],
  273. "conditions": [],
  274. "fields": ["title", "count()"],
  275. "range": "24h",
  276. "orderby": "time",
  277. },
  278. )
  279. assert response.status_code == 201, response.content
  280. assert response.data["projects"] == [-1]
  281. assert response.data["name"] == "All projects"
  282. def test_post_cannot_use_version_two_fields(self):
  283. with self.feature(self.feature_name):
  284. response = self.client.post(
  285. self.url,
  286. {
  287. "name": "New query",
  288. "projects": self.project_ids,
  289. "fields": ["id"],
  290. "range": "24h",
  291. "limit": 20,
  292. "environment": ["dev"],
  293. "yAxis": ["count(id)"],
  294. "aggregations": [],
  295. "orderby": "-time",
  296. },
  297. )
  298. assert response.status_code == 400, response.content
  299. assert (
  300. "You cannot use the environment, yAxis attribute(s) with the selected version"
  301. == response.data["non_field_errors"][0]
  302. )
  303. class DiscoverSavedQueriesVersion2Test(DiscoverSavedQueryBase):
  304. feature_name = "organizations:discover-query"
  305. def setUp(self):
  306. super().setUp()
  307. self.url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  308. def test_post_invalid_conditions(self):
  309. with self.feature(self.feature_name):
  310. response = self.client.post(
  311. self.url,
  312. {
  313. "name": "New query",
  314. "projects": self.project_ids,
  315. "fields": ["title", "count()"],
  316. "range": "24h",
  317. "version": 2,
  318. "conditions": [["field", "=", "value"]],
  319. },
  320. )
  321. assert response.status_code == 400, response.content
  322. assert (
  323. "You cannot use the conditions attribute(s) with the selected version"
  324. == response.data["non_field_errors"][0]
  325. )
  326. def test_post_require_selected_fields(self):
  327. with self.feature(self.feature_name):
  328. response = self.client.post(
  329. self.url,
  330. {
  331. "name": "New query",
  332. "projects": self.project_ids,
  333. "fields": [],
  334. "range": "24h",
  335. "version": 2,
  336. },
  337. )
  338. assert response.status_code == 400, response.content
  339. assert "You must include at least one field." == response.data["non_field_errors"][0]
  340. def test_post_success(self):
  341. with self.feature(self.feature_name):
  342. response = self.client.post(
  343. self.url,
  344. {
  345. "name": "new query",
  346. "projects": self.project_ids,
  347. "fields": ["title", "count()", "project"],
  348. "environment": ["dev"],
  349. "query": "event.type:error browser.name:Firefox",
  350. "range": "24h",
  351. "yAxis": ["count(id)"],
  352. "display": "releases",
  353. "version": 2,
  354. },
  355. )
  356. assert response.status_code == 201, response.content
  357. data = response.data
  358. assert data["fields"] == ["title", "count()", "project"]
  359. assert data["range"] == "24h"
  360. assert data["environment"] == ["dev"]
  361. assert data["query"] == "event.type:error browser.name:Firefox"
  362. assert data["yAxis"] == ["count(id)"]
  363. assert data["display"] == "releases"
  364. assert data["version"] == 2
  365. def test_post_all_projects(self):
  366. with self.feature(self.feature_name):
  367. response = self.client.post(
  368. self.url,
  369. {
  370. "name": "New query",
  371. "projects": [-1],
  372. "fields": ["title", "count()"],
  373. "range": "24h",
  374. "version": 2,
  375. },
  376. )
  377. assert response.status_code == 201, response.content
  378. assert response.data["projects"] == [-1]
  379. def test_save_with_project(self):
  380. with self.feature(self.feature_name):
  381. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  382. response = self.client.post(
  383. url,
  384. {
  385. "name": "project query",
  386. "projects": self.project_ids,
  387. "fields": ["title", "count()"],
  388. "range": "24h",
  389. "query": f"project:{self.projects[0].slug}",
  390. "version": 2,
  391. },
  392. )
  393. assert response.status_code == 201, response.content
  394. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  395. def test_save_with_project_and_my_projects(self):
  396. team = self.create_team(organization=self.org, members=[self.user])
  397. project = self.create_project(organization=self.org, teams=[team])
  398. with self.feature(self.feature_name):
  399. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  400. response = self.client.post(
  401. url,
  402. {
  403. "name": "project query",
  404. "projects": [],
  405. "fields": ["title", "count()"],
  406. "range": "24h",
  407. "query": f"project:{project.slug}",
  408. "version": 2,
  409. },
  410. )
  411. assert response.status_code == 201, response.content
  412. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  413. def test_save_with_org_projects(self):
  414. project = self.create_project(organization=self.org)
  415. with self.feature(self.feature_name):
  416. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  417. response = self.client.post(
  418. url,
  419. {
  420. "name": "project query",
  421. "projects": [project.id],
  422. "fields": ["title", "count()"],
  423. "range": "24h",
  424. "version": 2,
  425. },
  426. )
  427. assert response.status_code == 201, response.content
  428. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  429. def test_save_with_team_project(self):
  430. team = self.create_team(organization=self.org, members=[self.user])
  431. project = self.create_project(organization=self.org, teams=[team])
  432. self.create_project(organization=self.org, teams=[team])
  433. with self.feature(self.feature_name):
  434. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  435. response = self.client.post(
  436. url,
  437. {
  438. "name": "project query",
  439. "projects": [project.id],
  440. "fields": ["title", "count()"],
  441. "range": "24h",
  442. "version": 2,
  443. },
  444. )
  445. assert response.status_code == 201, response.content
  446. assert DiscoverSavedQuery.objects.filter(name="project query").exists()
  447. def test_save_without_team(self):
  448. team = self.create_team(organization=self.org, members=[])
  449. self.create_project(organization=self.org, teams=[team])
  450. with self.feature(self.feature_name):
  451. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  452. response = self.client.post(
  453. url,
  454. {
  455. "name": "without team query",
  456. "projects": [],
  457. "fields": ["title", "count()"],
  458. "range": "24h",
  459. "version": 2,
  460. },
  461. )
  462. assert response.status_code == 400
  463. assert "No Projects found, join a Team" == response.data["detail"]
  464. def test_save_with_team_and_without_project(self):
  465. team = self.create_team(organization=self.org, members=[self.user])
  466. self.create_project(organization=self.org, teams=[team])
  467. with self.feature(self.feature_name):
  468. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  469. response = self.client.post(
  470. url,
  471. {
  472. "name": "with team query",
  473. "projects": [],
  474. "fields": ["title", "count()"],
  475. "range": "24h",
  476. "version": 2,
  477. },
  478. )
  479. assert response.status_code == 201, response.content
  480. assert DiscoverSavedQuery.objects.filter(name="with team query").exists()
  481. def test_save_with_wrong_projects(self):
  482. other_org = self.create_organization(owner=self.user)
  483. project = self.create_project(organization=other_org)
  484. project2 = self.create_project(organization=self.org)
  485. with self.feature(self.feature_name):
  486. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  487. response = self.client.post(
  488. url,
  489. {
  490. "name": "project query",
  491. "projects": [project.id],
  492. "fields": ["title", "count()"],
  493. "range": "24h",
  494. "query": f"project:{project.slug}",
  495. "version": 2,
  496. },
  497. )
  498. assert response.status_code == 403, response.content
  499. assert not DiscoverSavedQuery.objects.filter(name="project query").exists()
  500. with self.feature(self.feature_name):
  501. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  502. response = self.client.post(
  503. url,
  504. {
  505. "name": "project query",
  506. "projects": [project.id, project2.id],
  507. "fields": ["title", "count()"],
  508. "range": "24h",
  509. "query": f"project:{project.slug} project:{project2.slug}",
  510. "version": 2,
  511. },
  512. )
  513. assert response.status_code == 403, response.content
  514. assert not DiscoverSavedQuery.objects.filter(name="project query").exists()
  515. # Mix of wrong + valid
  516. with self.feature(self.feature_name):
  517. url = reverse("sentry-api-0-discover-saved-queries", args=[self.org.slug])
  518. response = self.client.post(
  519. url,
  520. {
  521. "name": "project query",
  522. "projects": [-1],
  523. "fields": ["title", "count()"],
  524. "range": "24h",
  525. "query": f"project:{project.slug} project:{project2.slug}",
  526. "version": 2,
  527. },
  528. )
  529. assert response.status_code == 400, response.content
  530. assert not DiscoverSavedQuery.objects.filter(name="project query").exists()
  531. def test_save_with_equation(self):
  532. with self.feature(self.feature_name):
  533. response = self.client.post(
  534. self.url,
  535. {
  536. "name": "Equation query",
  537. "projects": [-1],
  538. "fields": [
  539. "title",
  540. "equation|count_if(measurements.lcp,greater,4000) / count()",
  541. "count()",
  542. "count_if(measurements.lcp,greater,4000)",
  543. ],
  544. "orderby": "equation[0]",
  545. "range": "24h",
  546. "query": "title:1",
  547. "version": 2,
  548. },
  549. )
  550. assert response.status_code == 201, response.content
  551. assert DiscoverSavedQuery.objects.filter(name="Equation query").exists()
  552. def test_save_with_invalid_equation(self):
  553. with self.feature(self.feature_name):
  554. response = self.client.post(
  555. self.url,
  556. {
  557. "name": "Equation query",
  558. "projects": [-1],
  559. "fields": [
  560. "title",
  561. "equation|count_if(measurements.lcp,greater,4000) / 0",
  562. "count()",
  563. "count_if(measurements.lcp,greater,4000)",
  564. ],
  565. "orderby": "equation[0]",
  566. "range": "24h",
  567. "query": "title:1",
  568. "version": 2,
  569. },
  570. )
  571. assert response.status_code == 400, response.content
  572. assert not DiscoverSavedQuery.objects.filter(name="Equation query").exists()
  573. def test_save_invalid_query(self):
  574. with self.feature(self.feature_name):
  575. response = self.client.post(
  576. self.url,
  577. {
  578. "name": "Bad query",
  579. "projects": [-1],
  580. "fields": ["title", "count()"],
  581. "range": "24h",
  582. "query": "spaceAfterColon: 1",
  583. "version": 2,
  584. },
  585. )
  586. assert response.status_code == 400, response.content
  587. assert not DiscoverSavedQuery.objects.filter(name="Bad query").exists()
  588. def test_save_invalid_query_orderby(self):
  589. with self.feature(self.feature_name):
  590. response = self.client.post(
  591. self.url,
  592. {
  593. "name": "Bad query",
  594. "projects": [-1],
  595. "fields": ["title", "count()"],
  596. "orderby": "fake()",
  597. "range": "24h",
  598. "query": "title:1",
  599. "version": 2,
  600. },
  601. )
  602. assert response.status_code == 400, response.content
  603. assert not DiscoverSavedQuery.objects.filter(name="Bad query").exists()
  604. def test_save_interval(self):
  605. with self.feature(self.feature_name):
  606. response = self.client.post(
  607. self.url,
  608. {
  609. "name": "Interval query",
  610. "projects": [-1],
  611. "fields": ["title", "count()"],
  612. "statsPeriod": "24h",
  613. "query": "spaceAfterColon:1",
  614. "version": 2,
  615. "interval": "1m",
  616. },
  617. )
  618. assert response.status_code == 201, response.content
  619. assert response.data["name"] == "Interval query"
  620. assert response.data["interval"] == "1m"
  621. def test_save_invalid_interval(self):
  622. with self.feature(self.feature_name):
  623. response = self.client.post(
  624. self.url,
  625. {
  626. "name": "Interval query",
  627. "projects": [-1],
  628. "fields": ["title", "count()"],
  629. "range": "24h",
  630. "query": "spaceAfterColon:1",
  631. "version": 2,
  632. "interval": "1s",
  633. },
  634. )
  635. assert response.status_code == 400, response.content