test_organization_events.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. from __future__ import absolute_import
  2. import six
  3. from six.moves.urllib.parse import urlencode
  4. from django.utils import timezone
  5. from django.core.urlresolvers import reverse
  6. from sentry.testutils import APITestCase, SnubaTestCase
  7. from sentry.testutils.helpers.datetime import before_now, iso_format
  8. from sentry.utils.compat import map
  9. class OrganizationEventsEndpointTest(APITestCase, SnubaTestCase):
  10. def setUp(self):
  11. super(OrganizationEventsEndpointTest, self).setUp()
  12. self.min_ago = iso_format(before_now(minutes=1))
  13. self.day_ago = iso_format(before_now(days=1))
  14. def assert_events_in_response(self, response, event_ids):
  15. assert sorted(map(lambda x: x["eventID"], response.data)) == sorted(event_ids)
  16. def test_simple(self):
  17. self.login_as(user=self.user)
  18. project = self.create_project()
  19. project2 = self.create_project()
  20. event_1 = self.store_event(
  21. data={"event_id": "a" * 32, "timestamp": self.min_ago}, project_id=project.id
  22. )
  23. event_2 = self.store_event(
  24. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project2.id
  25. )
  26. url = reverse(
  27. "sentry-api-0-organization-events",
  28. kwargs={"organization_slug": project.organization.slug},
  29. )
  30. response = self.client.get(url, format="json")
  31. assert response.status_code == 200, response.content
  32. assert len(response.data) == 2
  33. self.assert_events_in_response(response, [event_1.event_id, event_2.event_id])
  34. def test_simple_superuser(self):
  35. user = self.create_user(is_superuser=True)
  36. self.login_as(user=user, superuser=True)
  37. project = self.create_project()
  38. project2 = self.create_project()
  39. event_1 = self.store_event(
  40. data={"event_id": "a" * 32, "timestamp": iso_format(before_now(seconds=1))},
  41. project_id=project.id,
  42. )
  43. event_2 = self.store_event(
  44. data={"event_id": "b" * 32, "timestamp": iso_format(before_now(seconds=1))},
  45. project_id=project2.id,
  46. )
  47. url = reverse(
  48. "sentry-api-0-organization-events",
  49. kwargs={"organization_slug": project.organization.slug},
  50. )
  51. response = self.client.get(url, format="json")
  52. assert response.status_code == 200, response.content
  53. assert len(response.data) == 2
  54. self.assert_events_in_response(response, [event_1.event_id, event_2.event_id])
  55. def test_message_search_raw_text(self):
  56. self.login_as(user=self.user)
  57. project = self.create_project()
  58. self.store_event(
  59. data={"message": "how to make fast", "timestamp": iso_format(before_now(seconds=1))},
  60. project_id=project.id,
  61. )
  62. event_2 = self.store_event(
  63. data={"message": "Delet the Data", "timestamp": iso_format(before_now(seconds=1))},
  64. project_id=project.id,
  65. )
  66. url = reverse(
  67. "sentry-api-0-organization-events",
  68. kwargs={"organization_slug": project.organization.slug},
  69. )
  70. response = self.client.get(url, {"query": "delet"}, format="json")
  71. assert response.status_code == 200, response.content
  72. assert len(response.data) == 1
  73. assert response.data[0]["eventID"] == event_2.event_id
  74. assert response.data[0]["message"] == "Delet the Data"
  75. def test_message_search_tags(self):
  76. self.login_as(user=self.user)
  77. project = self.create_project()
  78. event_1 = self.store_event(
  79. data={
  80. "event_id": "a" * 32,
  81. "message": "how to make fast",
  82. "timestamp": iso_format(before_now(seconds=1)),
  83. "fingerprint": ["group-1"],
  84. },
  85. project_id=project.id,
  86. )
  87. event_2 = self.store_event(
  88. data={
  89. "event_id": "b" * 32,
  90. "message": "Delet the Data",
  91. "timestamp": iso_format(before_now(seconds=1)),
  92. "fingerprint": ["group-1"],
  93. "user": {"email": "foo@example.com"},
  94. },
  95. project_id=project.id,
  96. )
  97. url = reverse(
  98. "sentry-api-0-organization-events",
  99. kwargs={"organization_slug": project.organization.slug},
  100. )
  101. response = self.client.get(url, {"query": "user.email:foo@example.com"}, format="json")
  102. assert response.status_code == 200, response.content
  103. assert len(response.data) == 1
  104. assert response.data[0]["eventID"] == event_2.event_id
  105. assert response.data[0]["message"] == "Delet the Data"
  106. response = self.client.get(url, {"query": "!user.email:foo@example.com"}, format="json")
  107. assert response.status_code == 200, response.content
  108. assert len(response.data) == 1
  109. assert response.data[0]["eventID"] == event_1.event_id
  110. assert response.data[0]["message"] == "how to make fast"
  111. def test_invalid_search_terms(self):
  112. self.login_as(user=self.user)
  113. project = self.create_project()
  114. self.store_event(
  115. data={"event_id": "a" * 32, "message": "how to make fast"}, project_id=project.id
  116. )
  117. url = reverse(
  118. "sentry-api-0-organization-events",
  119. kwargs={"organization_slug": project.organization.slug},
  120. )
  121. response = self.client.get(url, {"query": "hi \n there"}, format="json")
  122. assert response.status_code == 400, response.content
  123. assert (
  124. response.data["detail"]
  125. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  126. )
  127. def test_project_filtering(self):
  128. user = self.create_user(is_staff=False, is_superuser=False)
  129. org = self.create_organization()
  130. org.flags.allow_joinleave = False
  131. org.save()
  132. team = self.create_team(organization=org)
  133. self.create_member(organization=org, user=user, teams=[team])
  134. self.login_as(user=user)
  135. project = self.create_project(organization=org, teams=[team])
  136. project2 = self.create_project(organization=org, teams=[team])
  137. project3 = self.create_project(organization=org)
  138. event_1 = self.store_event(
  139. data={"event_id": "a" * 32, "timestamp": self.min_ago}, project_id=project.id
  140. )
  141. event_2 = self.store_event(
  142. data={"event_id": "b" * 32, "timestamp": self.min_ago}, project_id=project2.id
  143. )
  144. self.store_event(
  145. data={"event_id": "c" * 32, "timestamp": self.min_ago}, project_id=project3.id
  146. )
  147. base_url = reverse(
  148. "sentry-api-0-organization-events",
  149. kwargs={"organization_slug": project.organization.slug},
  150. )
  151. # test bad project id
  152. url = "%s?project=abc" % (base_url,)
  153. response = self.client.get(url, format="json")
  154. assert response.status_code == 400
  155. # test including project user doesn't have access to
  156. url = "%s?project=%s&project=%s" % (base_url, project.id, project3.id)
  157. response = self.client.get(url, format="json")
  158. assert response.status_code == 403
  159. # test filtering by project
  160. url = "%s?project=%s" % (base_url, project.id)
  161. response = self.client.get(url, format="json")
  162. assert response.status_code == 200, response.content
  163. assert len(response.data) == 1
  164. self.assert_events_in_response(response, [event_1.event_id])
  165. # test only returns events from project user has access to
  166. response = self.client.get(base_url, format="json")
  167. assert response.status_code == 200, response.content
  168. assert len(response.data) == 2
  169. self.assert_events_in_response(response, [event_1.event_id, event_2.event_id])
  170. def test_stats_period(self):
  171. self.login_as(user=self.user)
  172. project = self.create_project()
  173. project2 = self.create_project()
  174. event_1 = self.store_event(
  175. data={"event_id": "a" * 32, "timestamp": self.min_ago}, project_id=project.id
  176. )
  177. self.store_event(
  178. data={"event_id": "b" * 32, "timestamp": self.day_ago}, project_id=project2.id
  179. )
  180. url = reverse(
  181. "sentry-api-0-organization-events",
  182. kwargs={"organization_slug": project.organization.slug},
  183. )
  184. url = "%s?statsPeriod=2h" % (url,)
  185. response = self.client.get(url, format="json")
  186. assert response.status_code == 200, response.content
  187. assert len(response.data) == 1
  188. self.assert_events_in_response(response, [event_1.event_id])
  189. def test_time_range(self):
  190. self.login_as(user=self.user)
  191. project = self.create_project()
  192. project2 = self.create_project()
  193. event_1 = self.store_event(
  194. data={"event_id": "a" * 32, "timestamp": self.min_ago}, project_id=project.id
  195. )
  196. self.store_event(
  197. data={"event_id": "b" * 32, "timestamp": self.day_ago}, project_id=project2.id
  198. )
  199. now = timezone.now()
  200. base_url = reverse(
  201. "sentry-api-0-organization-events",
  202. kwargs={"organization_slug": project.organization.slug},
  203. )
  204. # test swapped order of start/end
  205. url = "%s?%s" % (
  206. base_url,
  207. urlencode({"end": iso_format(before_now(hours=2)), "start": iso_format(now)}),
  208. )
  209. response = self.client.get(url, format="json")
  210. assert response.status_code == 400
  211. url = "%s?%s" % (
  212. base_url,
  213. urlencode({"start": iso_format(before_now(hours=2)), "end": iso_format(now)}),
  214. )
  215. response = self.client.get(url, format="json")
  216. assert response.status_code == 200, response.content
  217. assert len(response.data) == 1
  218. self.assert_events_in_response(response, [event_1.event_id])
  219. def test_environment_filtering(self):
  220. user = self.create_user()
  221. org = self.create_organization()
  222. team = self.create_team(organization=org)
  223. self.create_member(organization=org, user=user, teams=[team])
  224. self.login_as(user=user)
  225. project = self.create_project(organization=org, teams=[team])
  226. environment = self.create_environment(project=project, name="production")
  227. environment2 = self.create_environment(project=project)
  228. null_env = self.create_environment(project=project, name="")
  229. events = []
  230. for event_id, env in [
  231. ("a" * 32, environment),
  232. ("b" * 32, environment),
  233. ("c" * 32, environment2),
  234. ("d" * 32, null_env),
  235. ]:
  236. events.append(
  237. self.store_event(
  238. data={
  239. "event_id": event_id,
  240. "timestamp": self.min_ago,
  241. "fingerprint": ["put-me-in-group1"],
  242. "environment": env.name or None,
  243. },
  244. project_id=project.id,
  245. )
  246. )
  247. event_1, event_2, event_3, event_4 = events
  248. base_url = reverse(
  249. "sentry-api-0-organization-events", kwargs={"organization_slug": org.slug}
  250. )
  251. # test as part of query param
  252. url = "%s?environment=%s" % (base_url, environment.name)
  253. response = self.client.get(url, format="json")
  254. assert response.status_code == 200, response.content
  255. assert len(response.data) == 2
  256. self.assert_events_in_response(response, [event_1.event_id, event_2.event_id])
  257. # test multiple as part of query param
  258. url = "%s?%s" % (
  259. base_url,
  260. urlencode((("environment", environment.name), ("environment", environment2.name))),
  261. )
  262. response = self.client.get(url, format="json")
  263. assert response.status_code == 200, response.content
  264. assert len(response.data) == 3
  265. self.assert_events_in_response(
  266. response, [event_1.event_id, event_2.event_id, event_3.event_id]
  267. )
  268. # test multiple as part of query param with no env
  269. url = "%s?%s" % (
  270. base_url,
  271. urlencode((("environment", environment.name), ("environment", null_env.name))),
  272. )
  273. response = self.client.get(url, format="json")
  274. assert response.status_code == 200, response.content
  275. assert len(response.data) == 3
  276. self.assert_events_in_response(
  277. response, [event_1.event_id, event_2.event_id, event_4.event_id]
  278. )
  279. # test as part of search
  280. url = "%s?query=environment:%s" % (base_url, environment.name)
  281. response = self.client.get(url, format="json")
  282. assert response.status_code == 200, response.content
  283. assert len(response.data) == 2
  284. self.assert_events_in_response(response, [event_1.event_id, event_2.event_id])
  285. # test as part of search - no environment
  286. url = '%s?query=environment:""' % (base_url,)
  287. response = self.client.get(url, format="json")
  288. assert response.status_code == 200, response.content
  289. assert len(response.data) == 1
  290. self.assert_events_in_response(response, [event_4.event_id])
  291. # test nonexistent environment
  292. url = "%s?environment=notanenvironment" % (base_url,)
  293. response = self.client.get(url, format="json")
  294. assert response.status_code == 404
  295. def test_custom_tags(self):
  296. user = self.create_user()
  297. org = self.create_organization()
  298. team = self.create_team(organization=org)
  299. self.create_member(organization=org, user=user, teams=[team])
  300. self.login_as(user=user)
  301. project = self.create_project(organization=org, teams=[team])
  302. event_1 = self.store_event(
  303. data={
  304. "event_id": "a" * 32,
  305. "tags": {"fruit": "apple"},
  306. "fingerprint": ["group-1"],
  307. "timestamp": iso_format(before_now(seconds=1)),
  308. },
  309. project_id=project.id,
  310. )
  311. event_2 = self.store_event(
  312. data={
  313. "event_id": "b" * 32,
  314. "tags": {"fruit": "orange"},
  315. "fingerprint": ["group-1"],
  316. "timestamp": iso_format(before_now(seconds=1)),
  317. },
  318. project_id=project.id,
  319. )
  320. base_url = reverse(
  321. "sentry-api-0-organization-events", kwargs={"organization_slug": org.slug}
  322. )
  323. response = self.client.get("%s?query=fruit:apple" % (base_url,), format="json")
  324. assert response.status_code == 200, response.content
  325. assert len(response.data) == 1
  326. self.assert_events_in_response(response, [event_1.event_id])
  327. response = self.client.get("%s?query=!fruit:apple" % (base_url,), format="json")
  328. assert response.status_code == 200, response.content
  329. assert len(response.data) == 1
  330. self.assert_events_in_response(response, [event_2.event_id])
  331. def test_wildcard_search(self):
  332. user = self.create_user()
  333. org = self.create_organization()
  334. team = self.create_team(organization=org)
  335. self.create_member(organization=org, user=user, teams=[team])
  336. self.login_as(user=user)
  337. project = self.create_project(organization=org, teams=[team])
  338. event_1 = self.store_event(
  339. data={
  340. "event_id": "a" * 32,
  341. "tags": {"sentry:release": "3.1.2"},
  342. "timestamp": self.min_ago,
  343. },
  344. project_id=project.id,
  345. )
  346. event_2 = self.store_event(
  347. data={
  348. "event_id": "b" * 32,
  349. "tags": {"sentry:release": "4.1.2"},
  350. "timestamp": self.min_ago,
  351. },
  352. project_id=project.id,
  353. )
  354. event_3 = self.store_event(
  355. data={
  356. "event_id": "c" * 32,
  357. "user": {"email": "foo@example.com"},
  358. "timestamp": self.min_ago,
  359. },
  360. project_id=project.id,
  361. )
  362. event_4 = self.store_event(
  363. data={
  364. "event_id": "d" * 32,
  365. "user": {"email": "foo@example.commmmmmmm"},
  366. "timestamp": self.min_ago,
  367. },
  368. project_id=project.id,
  369. )
  370. base_url = reverse(
  371. "sentry-api-0-organization-events", kwargs={"organization_slug": org.slug}
  372. )
  373. response = self.client.get("%s?query=release:3.1.*" % (base_url,), format="json")
  374. assert response.status_code == 200, response.content
  375. assert len(response.data) == 1
  376. self.assert_events_in_response(response, [event_1.event_id])
  377. response = self.client.get("%s?query=!release:3.1.*" % (base_url,), format="json")
  378. assert response.status_code == 200, response.content
  379. assert len(response.data) == 3
  380. self.assert_events_in_response(
  381. response, [event_2.event_id, event_3.event_id, event_4.event_id]
  382. )
  383. response = self.client.get("%s?query=user.email:*@example.com" % (base_url,), format="json")
  384. assert response.status_code == 200, response.content
  385. assert len(response.data) == 1
  386. self.assert_events_in_response(response, [event_3.event_id])
  387. response = self.client.get(
  388. "%s?query=!user.email:*@example.com" % (base_url,), format="json"
  389. )
  390. assert response.status_code == 200, response.content
  391. assert len(response.data) == 3
  392. self.assert_events_in_response(
  393. response, [event_1.event_id, event_2.event_id, event_4.event_id]
  394. )
  395. def test_has_tag(self):
  396. user = self.create_user()
  397. org = self.create_organization()
  398. team = self.create_team(organization=org)
  399. self.create_member(organization=org, user=user, teams=[team])
  400. self.login_as(user=user)
  401. project = self.create_project(organization=org, teams=[team])
  402. event_1 = self.store_event(
  403. data={
  404. "event_id": "a" * 32,
  405. "user": {"email": "foo@example.com"},
  406. "timestamp": self.min_ago,
  407. },
  408. project_id=project.id,
  409. )
  410. event_2 = self.store_event(
  411. data={
  412. "event_id": "b" * 32,
  413. "tags": {"example_tag": "example_value"},
  414. "timestamp": self.min_ago,
  415. },
  416. project_id=project.id,
  417. )
  418. base_url = reverse(
  419. "sentry-api-0-organization-events", kwargs={"organization_slug": org.slug}
  420. )
  421. response = self.client.get("%s?query=has:user.email" % (base_url,), format="json")
  422. assert response.status_code == 200, response.content
  423. assert len(response.data) == 1
  424. self.assert_events_in_response(response, [event_1.event_id])
  425. # test custom tag
  426. response = self.client.get("%s?query=has:example_tag" % (base_url,), format="json")
  427. assert response.status_code == 200, response.content
  428. assert len(response.data) == 1
  429. self.assert_events_in_response(response, [event_2.event_id])
  430. response = self.client.get("%s?query=!has:user.email" % (base_url,), format="json")
  431. assert response.status_code == 200, response.content
  432. assert len(response.data) == 1
  433. self.assert_events_in_response(response, [event_2.event_id])
  434. # test custom tag
  435. response = self.client.get("%s?query=!has:example_tag" % (base_url,), format="json")
  436. assert response.status_code == 200, response.content
  437. assert len(response.data) == 1
  438. self.assert_events_in_response(response, [event_1.event_id])
  439. def test_no_projects(self):
  440. org = self.create_organization(owner=self.user)
  441. self.login_as(user=self.user)
  442. url = reverse("sentry-api-0-organization-events", kwargs={"organization_slug": org.slug})
  443. response = self.client.get(url, format="json")
  444. assert response.status_code == 200, response.content
  445. assert len(response.data) == 0
  446. def test_event_id_direct_hit(self):
  447. user = self.create_user()
  448. org = self.create_organization()
  449. team = self.create_team(organization=org)
  450. self.create_member(organization=org, user=user, teams=[team])
  451. self.login_as(user=user)
  452. project = self.create_project(organization=org, teams=[team])
  453. self.store_event(
  454. data={"event_id": "a" * 32, "message": "best event", "timestamp": self.min_ago},
  455. project_id=project.id,
  456. )
  457. url = reverse("sentry-api-0-organization-events", kwargs={"organization_slug": org.slug})
  458. response = self.client.get(url, {"query": "a" * 32}, format="json")
  459. assert response.status_code == 200, response.content
  460. assert len(response.data) == 1
  461. assert response["X-Sentry-Direct-Hit"] == "1"
  462. def test_event_id_direct_hit_miss(self):
  463. user = self.create_user()
  464. org = self.create_organization()
  465. team = self.create_team(organization=org)
  466. self.create_member(organization=org, user=user, teams=[team])
  467. self.login_as(user=user)
  468. self.create_project(organization=org, teams=[team])
  469. url = reverse("sentry-api-0-organization-events", kwargs={"organization_slug": org.slug})
  470. response = self.client.get(url, {"query": "a" * 32}, format="json")
  471. assert response.status_code == 200, response.content
  472. assert len(response.data) == 0
  473. def test_project_id_filter(self):
  474. team = self.create_team(organization=self.organization, members=[self.user])
  475. project = self.create_project(organization=self.organization, teams=[team])
  476. self.store_event(
  477. data={"event_id": "a" * 32, "message": "best event", "timestamp": self.min_ago},
  478. project_id=project.id,
  479. )
  480. url = reverse(
  481. "sentry-api-0-organization-events", kwargs={"organization_slug": self.organization.slug}
  482. )
  483. self.login_as(user=self.user)
  484. response = self.client.get(
  485. url, {"query": "project_id:{}".format(project.id)}, format="json"
  486. )
  487. assert response.status_code == 200, response.content
  488. assert len(response.data) == 1
  489. assert response.data[0]["projectID"] == six.text_type(project.id)
  490. response = self.client.get(url, {"query": "project_id:9"}, format="json")
  491. # project_id filter should apply
  492. assert response.status_code == 200, response.content
  493. assert len(response.data) == 0