test_discover_saved_queries.py 27 KB

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