test_organization_events_v2.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. from __future__ import absolute_import
  2. from django.core.urlresolvers import reverse
  3. from sentry.testutils import APITestCase, SnubaTestCase
  4. from sentry.testutils.helpers.datetime import before_now, iso_format
  5. from sentry.utils.samples import load_data
  6. import pytest
  7. class OrganizationEventsV2EndpointTest(APITestCase, SnubaTestCase):
  8. def setUp(self):
  9. super(OrganizationEventsV2EndpointTest, self).setUp()
  10. self.min_ago = iso_format(before_now(minutes=1))
  11. self.two_min_ago = iso_format(before_now(minutes=2))
  12. self.url = reverse(
  13. "sentry-api-0-organization-eventsv2",
  14. kwargs={"organization_slug": self.organization.slug},
  15. )
  16. def test_no_projects(self):
  17. self.login_as(user=self.user)
  18. with self.feature("organizations:events-v2"):
  19. response = self.client.get(self.url, format="json")
  20. assert response.status_code == 200, response.content
  21. assert len(response.data) == 0
  22. def test_multi_project_feature_gate_rejection(self):
  23. self.login_as(user=self.user)
  24. team = self.create_team(organization=self.organization, members=[self.user])
  25. project = self.create_project(organization=self.organization, teams=[team])
  26. project2 = self.create_project(organization=self.organization, teams=[team])
  27. self.store_event(
  28. data={"event_id": "a" * 32, "timestamp": self.min_ago, "fingerprint": ["group1"]},
  29. project_id=project.id,
  30. )
  31. self.store_event(
  32. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group2"]},
  33. project_id=project2.id,
  34. )
  35. query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
  36. with self.feature({"organizations:events-v2": True, "organizations:global-views": False}):
  37. response = self.client.get(self.url, query, format="json")
  38. assert response.status_code == 400
  39. assert "events from multiple projects" in response.data["detail"]
  40. def test_invalid_search_terms(self):
  41. self.login_as(user=self.user)
  42. project = self.create_project()
  43. self.store_event(
  44. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  45. project_id=project.id,
  46. )
  47. with self.feature("organizations:events-v2"):
  48. response = self.client.get(self.url, {"query": "hi \n there"}, format="json")
  49. assert response.status_code == 400, response.content
  50. assert (
  51. response.data["detail"]
  52. == "Parse error: 'search' (column 4). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes."
  53. )
  54. def test_raw_data(self):
  55. self.login_as(user=self.user)
  56. project = self.create_project()
  57. self.store_event(
  58. data={
  59. "event_id": "a" * 32,
  60. "environment": "staging",
  61. "timestamp": self.two_min_ago,
  62. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  63. },
  64. project_id=project.id,
  65. )
  66. self.store_event(
  67. data={
  68. "event_id": "b" * 32,
  69. "environment": "staging",
  70. "timestamp": self.min_ago,
  71. "user": {"ip_address": "127.0.0.1", "email": "foo@example.com"},
  72. },
  73. project_id=project.id,
  74. )
  75. with self.feature("organizations:events-v2"):
  76. response = self.client.get(
  77. self.url,
  78. format="json",
  79. data={
  80. "field": ["id", "project.id", "user.email", "user.ip", "timestamp"],
  81. "orderby": "-timestamp",
  82. },
  83. )
  84. assert response.status_code == 200, response.content
  85. data = response.data["data"]
  86. assert len(data) == 2
  87. assert data[0]["id"] == "b" * 32
  88. assert data[0]["project.id"] == project.id
  89. assert data[0]["user.email"] == "foo@example.com"
  90. meta = response.data["meta"]
  91. assert meta["id"] == "string"
  92. assert meta["project.name"] == "string"
  93. assert meta["user.email"] == "string"
  94. assert meta["user.ip"] == "string"
  95. assert meta["timestamp"] == "date"
  96. def test_project_name(self):
  97. self.login_as(user=self.user)
  98. project = self.create_project()
  99. self.store_event(
  100. data={"event_id": "a" * 32, "environment": "staging", "timestamp": self.min_ago},
  101. project_id=project.id,
  102. )
  103. with self.feature("organizations:events-v2"):
  104. response = self.client.get(
  105. self.url, format="json", data={"field": ["project.name", "environment"]}
  106. )
  107. assert response.status_code == 200, response.content
  108. assert len(response.data["data"]) == 1
  109. assert response.data["data"][0]["project.name"] == project.slug
  110. assert "project.id" not in response.data["data"][0]
  111. assert response.data["data"][0]["environment"] == "staging"
  112. def test_implicit_groupby(self):
  113. self.login_as(user=self.user)
  114. project = self.create_project()
  115. self.store_event(
  116. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  117. project_id=project.id,
  118. )
  119. event1 = self.store_event(
  120. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_1"]},
  121. project_id=project.id,
  122. )
  123. event2 = self.store_event(
  124. data={"event_id": "c" * 32, "timestamp": self.min_ago, "fingerprint": ["group_2"]},
  125. project_id=project.id,
  126. )
  127. with self.feature("organizations:events-v2"):
  128. response = self.client.get(
  129. self.url,
  130. format="json",
  131. data={"field": ["count(id)", "project.id", "issue.id"], "orderby": "issue.id"},
  132. )
  133. assert response.status_code == 200, response.content
  134. assert len(response.data["data"]) == 2
  135. data = response.data["data"]
  136. assert data[0] == {
  137. "project.id": project.id,
  138. "project.name": project.slug,
  139. "issue.id": event1.group_id,
  140. "count_id": 2,
  141. "latest_event": event1.event_id,
  142. }
  143. assert data[1] == {
  144. "project.id": project.id,
  145. "project.name": project.slug,
  146. "issue.id": event2.group_id,
  147. "count_id": 1,
  148. "latest_event": event2.event_id,
  149. }
  150. meta = response.data["meta"]
  151. assert meta["count_id"] == "integer"
  152. def test_automatic_id_and_project(self):
  153. self.login_as(user=self.user)
  154. project = self.create_project()
  155. self.store_event(
  156. data={"event_id": "a" * 32, "timestamp": self.two_min_ago, "fingerprint": ["group_1"]},
  157. project_id=project.id,
  158. )
  159. event = self.store_event(
  160. data={"event_id": "b" * 32, "timestamp": self.min_ago, "fingerprint": ["group_1"]},
  161. project_id=project.id,
  162. )
  163. with self.feature("organizations:events-v2"):
  164. response = self.client.get(self.url, format="json", data={"field": ["count(id)"]})
  165. assert response.status_code == 200, response.content
  166. assert len(response.data["data"]) == 1
  167. data = response.data["data"]
  168. assert data[0] == {
  169. "project.name": project.slug,
  170. "count_id": 2,
  171. "latest_event": event.event_id,
  172. }
  173. meta = response.data["meta"]
  174. assert meta["count_id"] == "integer"
  175. assert meta["project.name"] == "string"
  176. assert meta["latest_event"] == "string"
  177. def test_orderby(self):
  178. self.login_as(user=self.user)
  179. project = self.create_project()
  180. self.store_event(
  181. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  182. )
  183. self.store_event(
  184. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project.id
  185. )
  186. self.store_event(
  187. data={"event_id": "c" * 32, "timestamp": self.min_ago}, project_id=project.id
  188. )
  189. with self.feature("organizations:events-v2"):
  190. response = self.client.get(
  191. self.url,
  192. format="json",
  193. data={"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]},
  194. )
  195. assert response.status_code == 200, response.content
  196. data = response.data["data"]
  197. assert data[0]["id"] == "c" * 32
  198. assert data[1]["id"] == "b" * 32
  199. assert data[2]["id"] == "a" * 32
  200. def test_sort_title(self):
  201. self.login_as(user=self.user)
  202. project = self.create_project()
  203. self.store_event(
  204. data={"event_id": "a" * 32, "message": "zlast", "timestamp": self.two_min_ago},
  205. project_id=project.id,
  206. )
  207. self.store_event(
  208. data={"event_id": "b" * 32, "message": "second", "timestamp": self.min_ago},
  209. project_id=project.id,
  210. )
  211. self.store_event(
  212. data={"event_id": "c" * 32, "message": "first", "timestamp": self.min_ago},
  213. project_id=project.id,
  214. )
  215. with self.feature("organizations:events-v2"):
  216. response = self.client.get(
  217. self.url, format="json", data={"field": ["id", "title"], "sort": "title"}
  218. )
  219. assert response.status_code == 200, response.content
  220. data = response.data["data"]
  221. assert data[0]["id"] == "c" * 32
  222. assert data[1]["id"] == "b" * 32
  223. assert data[2]["id"] == "a" * 32
  224. def test_sort_invalid(self):
  225. self.login_as(user=self.user)
  226. project = self.create_project()
  227. self.store_event(
  228. data={"event_id": "a" * 32, "timestamp": self.two_min_ago}, project_id=project.id
  229. )
  230. with self.feature("organizations:events-v2"):
  231. response = self.client.get(
  232. self.url, format="json", data={"field": ["id"], "sort": "garbage"}
  233. )
  234. assert response.status_code == 400
  235. assert "order by" in response.content
  236. def test_aliased_fields(self):
  237. self.login_as(user=self.user)
  238. project = self.create_project()
  239. event1 = self.store_event(
  240. data={
  241. "event_id": "a" * 32,
  242. "timestamp": self.min_ago,
  243. "fingerprint": ["group_1"],
  244. "user": {"email": "foo@example.com"},
  245. },
  246. project_id=project.id,
  247. )
  248. event2 = self.store_event(
  249. data={
  250. "event_id": "b" * 32,
  251. "timestamp": self.min_ago,
  252. "fingerprint": ["group_2"],
  253. "user": {"email": "foo@example.com"},
  254. },
  255. project_id=project.id,
  256. )
  257. self.store_event(
  258. data={
  259. "event_id": "c" * 32,
  260. "timestamp": self.min_ago,
  261. "fingerprint": ["group_2"],
  262. "user": {"email": "bar@example.com"},
  263. },
  264. project_id=project.id,
  265. )
  266. with self.feature("organizations:events-v2"):
  267. response = self.client.get(
  268. self.url,
  269. format="json",
  270. data={
  271. "field": ["issue.id", "issue_title", "count(id)", "count_unique(user)"],
  272. "orderby": "issue.id",
  273. },
  274. )
  275. assert response.status_code == 200, response.content
  276. assert len(response.data["data"]) == 2
  277. data = response.data["data"]
  278. assert data[0]["issue.id"] == event1.group_id
  279. assert data[0]["count_id"] == 1
  280. assert data[0]["count_unique_user"] == 1
  281. assert "latest_event" in data[0]
  282. assert "project.name" in data[0]
  283. assert "projectid" not in data[0]
  284. assert "project.id" not in data[0]
  285. assert data[1]["issue.id"] == event2.group_id
  286. assert data[1]["count_id"] == 2
  287. assert data[1]["count_unique_user"] == 2
  288. @pytest.mark.xfail(reason="aggregate comparisons need parser improvements")
  289. def test_aggregation_comparison(self):
  290. self.login_as(user=self.user)
  291. project = self.create_project()
  292. self.store_event(
  293. data={
  294. "event_id": "a" * 32,
  295. "timestamp": self.min_ago,
  296. "fingerprint": ["group_1"],
  297. "user": {"email": "foo@example.com"},
  298. },
  299. project_id=project.id,
  300. )
  301. event = self.store_event(
  302. data={
  303. "event_id": "b" * 32,
  304. "timestamp": self.min_ago,
  305. "fingerprint": ["group_2"],
  306. "user": {"email": "foo@example.com"},
  307. },
  308. project_id=project.id,
  309. )
  310. self.store_event(
  311. data={
  312. "event_id": "c" * 32,
  313. "timestamp": self.min_ago,
  314. "fingerprint": ["group_2"],
  315. "user": {"email": "bar@example.com"},
  316. },
  317. project_id=project.id,
  318. )
  319. self.store_event(
  320. data={
  321. "event_id": "d" * 32,
  322. "timestamp": self.min_ago,
  323. "fingerprint": ["group_3"],
  324. "user": {"email": "bar@example.com"},
  325. },
  326. project_id=project.id,
  327. )
  328. self.store_event(
  329. data={
  330. "event_id": "e" * 32,
  331. "timestamp": self.min_ago,
  332. "fingerprint": ["group_3"],
  333. "user": {"email": "bar@example.com"},
  334. },
  335. project_id=project.id,
  336. )
  337. with self.feature("organizations:events-v2"):
  338. response = self.client.get(
  339. self.url,
  340. format="json",
  341. data={
  342. "field": ["issue_title", "count(id)", "count_unique(user)"],
  343. "query": "count_id:>1 count_unique_user:>1",
  344. "orderby": "issue_title",
  345. },
  346. )
  347. assert response.status_code == 200, response.content
  348. assert len(response.data["data"]) == 1
  349. data = response.data["data"]
  350. assert data[0]["issue.id"] == event.group_id
  351. assert data[0]["count_id"] == 2
  352. assert data[0]["count_unique_user"] == 2
  353. @pytest.mark.xfail(reason="aggregate comparisons need parser improvements")
  354. def test_aggregation_comparison_with_conditions(self):
  355. self.login_as(user=self.user)
  356. project = self.create_project()
  357. self.store_event(
  358. data={
  359. "event_id": "a" * 32,
  360. "timestamp": self.min_ago,
  361. "fingerprint": ["group_1"],
  362. "user": {"email": "foo@example.com"},
  363. "environment": "prod",
  364. },
  365. project_id=project.id,
  366. )
  367. event = self.store_event(
  368. data={
  369. "event_id": "b" * 32,
  370. "timestamp": self.min_ago,
  371. "fingerprint": ["group_2"],
  372. "user": {"email": "foo@example.com"},
  373. "environment": "staging",
  374. },
  375. project_id=project.id,
  376. )
  377. self.store_event(
  378. data={
  379. "event_id": "c" * 32,
  380. "timestamp": self.min_ago,
  381. "fingerprint": ["group_2"],
  382. "user": {"email": "foo@example.com"},
  383. "environment": "prod",
  384. },
  385. project_id=project.id,
  386. )
  387. self.store_event(
  388. data={
  389. "event_id": "d" * 32,
  390. "timestamp": self.min_ago,
  391. "fingerprint": ["group_2"],
  392. "user": {"email": "foo@example.com"},
  393. "environment": "prod",
  394. },
  395. project_id=project.id,
  396. )
  397. with self.feature("organizations:events-v2"):
  398. response = self.client.get(
  399. self.url,
  400. format="json",
  401. data={
  402. "field": ["issue_title", "count(id)"],
  403. "query": "count_id:>1 user.email:foo@example.com environment:prod",
  404. "orderby": "issue_title",
  405. },
  406. )
  407. assert response.status_code == 200, response.content
  408. assert len(response.data["data"]) == 1
  409. data = response.data["data"]
  410. assert data[0]["issue.id"] == event.group_id
  411. assert data[0]["count_id"] == 2
  412. def test_nonexistent_fields(self):
  413. self.login_as(user=self.user)
  414. project = self.create_project()
  415. self.store_event(
  416. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  417. project_id=project.id,
  418. )
  419. with self.feature("organizations:events-v2"):
  420. response = self.client.get(self.url, format="json", data={"field": ["issue_world.id"]})
  421. assert response.status_code == 200, response.content
  422. assert response.data["data"][0]["issue_world.id"] == ""
  423. def test_no_requested_fields_or_grouping(self):
  424. self.login_as(user=self.user)
  425. project = self.create_project()
  426. self.store_event(
  427. data={"event_id": "a" * 32, "message": "how to make fast", "timestamp": self.min_ago},
  428. project_id=project.id,
  429. )
  430. with self.feature("organizations:events-v2"):
  431. response = self.client.get(self.url, format="json", data={"query": "test"})
  432. assert response.status_code == 400, response.content
  433. assert response.data["detail"] == "No fields provided"
  434. def test_condition_on_aggregate_misses(self):
  435. self.login_as(user=self.user)
  436. project = self.create_project()
  437. self.store_event(
  438. data={
  439. "event_id": "c" * 32,
  440. "timestamp": self.min_ago,
  441. "fingerprint": ["group_2"],
  442. "user": {"email": "bar@example.com"},
  443. },
  444. project_id=project.id,
  445. )
  446. with self.feature("organizations:events-v2"):
  447. response = self.client.get(
  448. self.url,
  449. format="json",
  450. data={"field": ["issue.id"], "query": "event_count:>0", "orderby": "issue.id"},
  451. )
  452. assert response.status_code == 200, response.content
  453. assert len(response.data["data"]) == 0
  454. def test_reference_event(self):
  455. self.login_as(user=self.user)
  456. project = self.create_project()
  457. reference = self.store_event(
  458. data={
  459. "event_id": "a" * 32,
  460. "transaction": "/example",
  461. "message": "how to make fast",
  462. "timestamp": self.two_min_ago,
  463. },
  464. project_id=project.id,
  465. )
  466. self.store_event(
  467. data={
  468. "event_id": "b" * 32,
  469. "transaction": "/example",
  470. "message": "how to make more faster?",
  471. "timestamp": self.min_ago,
  472. },
  473. project_id=project.id,
  474. )
  475. self.store_event(
  476. data={
  477. "event_id": "c" * 32,
  478. "transaction": "/nomatch",
  479. "message": "how to make fast",
  480. "timestamp": self.min_ago,
  481. },
  482. project_id=project.id,
  483. )
  484. with self.feature("organizations:events-v2"):
  485. response = self.client.get(
  486. self.url,
  487. format="json",
  488. data={
  489. "field": ["transaction", "count()"],
  490. "query": "",
  491. "referenceEvent": "{}:{}".format(project.slug, reference.event_id),
  492. },
  493. )
  494. assert response.status_code == 200, response.content
  495. assert len(response.data["data"]) == 1
  496. data = response.data["data"]
  497. assert data[0]["transaction"] == "/example"
  498. assert data[0]["latest_event"] == "b" * 32
  499. def test_stack_wildcard_condition(self):
  500. self.login_as(user=self.user)
  501. project = self.create_project()
  502. data = load_data("javascript")
  503. data["timestamp"] = self.min_ago
  504. self.store_event(data=data, project_id=project.id)
  505. with self.feature("organizations:events-v2"):
  506. response = self.client.get(
  507. self.url,
  508. format="json",
  509. data={"field": ["stack.filename", "message"], "query": "stack.filename:*.js"},
  510. )
  511. assert response.status_code == 200, response.content
  512. assert len(response.data["data"]) == 1
  513. assert response.data["meta"]["message"] == "string"
  514. def test_transaction_event_type(self):
  515. self.login_as(user=self.user)
  516. project = self.create_project()
  517. data = load_data("transaction")
  518. data["timestamp"] = iso_format(before_now(minutes=1))
  519. data["start_timestamp"] = iso_format(before_now(minutes=1, seconds=5))
  520. self.store_event(data=data, project_id=project.id)
  521. with self.feature("organizations:events-v2"):
  522. response = self.client.get(
  523. self.url,
  524. format="json",
  525. data={
  526. "field": ["transaction", "transaction.duration", "transaction.status"],
  527. "query": "event.type:transaction",
  528. },
  529. )
  530. assert response.status_code == 200, response.content
  531. assert len(response.data["data"]) == 1
  532. assert response.data["meta"]["transaction.duration"] == "duration"
  533. assert response.data["meta"]["transaction.status"] == "string"
  534. assert response.data["data"][0]["transaction.status"] == "ok"