test_organization_events_facets.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. from __future__ import absolute_import
  2. from datetime import timedelta
  3. from django.utils import timezone
  4. from django.core.urlresolvers import reverse
  5. from uuid import uuid4
  6. from sentry.testutils import APITestCase, SnubaTestCase
  7. from sentry.testutils.helpers.datetime import before_now, iso_format
  8. class OrganizationEventsFacetsEndpointTest(SnubaTestCase, APITestCase):
  9. feature_list = ("organizations:events-v2", "organizations:global-views")
  10. def setUp(self):
  11. super(OrganizationEventsFacetsEndpointTest, self).setUp()
  12. self.min_ago = before_now(minutes=1).replace(microsecond=0)
  13. self.day_ago = before_now(days=1).replace(microsecond=0)
  14. self.login_as(user=self.user)
  15. self.project = self.create_project()
  16. self.project2 = self.create_project()
  17. self.url = reverse(
  18. "sentry-api-0-organization-events-facets",
  19. kwargs={"organization_slug": self.project.organization.slug},
  20. )
  21. self.min_ago_iso = iso_format(self.min_ago)
  22. def test_simple(self):
  23. self.store_event(
  24. data={
  25. "event_id": uuid4().hex,
  26. "timestamp": self.min_ago_iso,
  27. "tags": {"number": "one"},
  28. },
  29. project_id=self.project2.id,
  30. )
  31. self.store_event(
  32. data={
  33. "event_id": uuid4().hex,
  34. "timestamp": self.min_ago_iso,
  35. "tags": {"number": "one"},
  36. },
  37. project_id=self.project.id,
  38. )
  39. self.store_event(
  40. data={
  41. "event_id": uuid4().hex,
  42. "timestamp": self.min_ago_iso,
  43. "tags": {"number": "two"},
  44. },
  45. project_id=self.project.id,
  46. )
  47. with self.feature(self.feature_list):
  48. response = self.client.get(self.url, format="json")
  49. assert response.status_code == 200, response.content
  50. expected = [
  51. {"count": 2, "name": "one", "value": "one"},
  52. {"count": 1, "name": "two", "value": "two"},
  53. ]
  54. self.assert_facet(response, "number", expected)
  55. def test_with_message_query(self):
  56. self.store_event(
  57. data={
  58. "event_id": uuid4().hex,
  59. "timestamp": self.min_ago_iso,
  60. "message": "how to make fast",
  61. "tags": {"color": "green"},
  62. },
  63. project_id=self.project.id,
  64. )
  65. self.store_event(
  66. data={
  67. "event_id": uuid4().hex,
  68. "timestamp": self.min_ago_iso,
  69. "message": "Delet the Data",
  70. "tags": {"color": "red"},
  71. },
  72. project_id=self.project.id,
  73. )
  74. self.store_event(
  75. data={
  76. "event_id": uuid4().hex,
  77. "timestamp": self.min_ago_iso,
  78. "message": "Data the Delet ",
  79. "tags": {"color": "yellow"},
  80. },
  81. project_id=self.project2.id,
  82. )
  83. with self.feature(self.feature_list):
  84. response = self.client.get(self.url, {"query": "delet"}, format="json")
  85. assert response.status_code == 200, response.content
  86. expected = [
  87. {"count": 1, "name": "yellow", "value": "yellow"},
  88. {"count": 1, "name": "red", "value": "red"},
  89. ]
  90. self.assert_facet(response, "color", expected)
  91. def test_with_condition(self):
  92. self.store_event(
  93. data={
  94. "event_id": uuid4().hex,
  95. "timestamp": self.min_ago_iso,
  96. "message": "how to make fast",
  97. "tags": {"color": "green"},
  98. },
  99. project_id=self.project.id,
  100. )
  101. self.store_event(
  102. data={
  103. "event_id": uuid4().hex,
  104. "timestamp": self.min_ago_iso,
  105. "message": "Delet the Data",
  106. "tags": {"color": "red"},
  107. },
  108. project_id=self.project.id,
  109. )
  110. self.store_event(
  111. data={
  112. "event_id": uuid4().hex,
  113. "timestamp": self.min_ago_iso,
  114. "message": "Data the Delet ",
  115. "tags": {"color": "yellow"},
  116. },
  117. project_id=self.project2.id,
  118. )
  119. with self.feature(self.feature_list):
  120. response = self.client.get(self.url, {"query": "color:yellow"}, format="json")
  121. assert response.status_code == 200, response.content
  122. expected = [{"count": 1, "name": "yellow", "value": "yellow"}]
  123. self.assert_facet(response, "color", expected)
  124. def test_start_end(self):
  125. two_days_ago = self.day_ago - timedelta(days=1)
  126. hour_ago = self.min_ago - timedelta(hours=1)
  127. two_hours_ago = hour_ago - timedelta(hours=1)
  128. self.store_event(
  129. data={
  130. "event_id": uuid4().hex,
  131. "timestamp": iso_format(two_days_ago),
  132. "tags": {"color": "red"},
  133. },
  134. project_id=self.project.id,
  135. )
  136. self.store_event(
  137. data={
  138. "event_id": uuid4().hex,
  139. "timestamp": iso_format(hour_ago),
  140. "tags": {"color": "red"},
  141. },
  142. project_id=self.project.id,
  143. )
  144. self.store_event(
  145. data={
  146. "event_id": uuid4().hex,
  147. "timestamp": iso_format(two_hours_ago),
  148. "tags": {"color": "red"},
  149. },
  150. project_id=self.project.id,
  151. )
  152. self.store_event(
  153. data={
  154. "event_id": uuid4().hex,
  155. "timestamp": iso_format(timezone.now()),
  156. "tags": {"color": "red"},
  157. },
  158. project_id=self.project2.id,
  159. )
  160. with self.feature(self.feature_list):
  161. response = self.client.get(
  162. self.url,
  163. {"start": iso_format(self.day_ago), "end": iso_format(self.min_ago)},
  164. format="json",
  165. )
  166. assert response.status_code == 200, response.content
  167. expected = [{"count": 2, "name": "red", "value": "red"}]
  168. self.assert_facet(response, "color", expected)
  169. def test_excluded_tag(self):
  170. self.user = self.create_user()
  171. self.user2 = self.create_user()
  172. self.store_event(
  173. data={
  174. "event_id": uuid4().hex,
  175. "timestamp": iso_format(self.day_ago),
  176. "message": "very bad",
  177. "tags": {"sentry:user": self.user.email},
  178. },
  179. project_id=self.project.id,
  180. )
  181. self.store_event(
  182. data={
  183. "event_id": uuid4().hex,
  184. "timestamp": iso_format(self.day_ago),
  185. "message": "very bad",
  186. "tags": {"sentry:user": self.user2.email},
  187. },
  188. project_id=self.project.id,
  189. )
  190. self.store_event(
  191. data={
  192. "event_id": uuid4().hex,
  193. "timestamp": iso_format(self.day_ago),
  194. "message": "very bad",
  195. "tags": {"sentry:user": self.user2.email},
  196. },
  197. project_id=self.project.id,
  198. )
  199. with self.feature(self.feature_list):
  200. response = self.client.get(self.url, format="json", data={"project": [self.project.id]})
  201. assert response.status_code == 200, response.content
  202. expected = [
  203. {"count": 2, "name": self.user2.email, "value": self.user2.email},
  204. {"count": 1, "name": self.user.email, "value": self.user.email},
  205. ]
  206. self.assert_facet(response, "user", expected)
  207. def test_no_projects(self):
  208. org = self.create_organization(owner=self.user)
  209. url = reverse(
  210. "sentry-api-0-organization-events-distribution", kwargs={"organization_slug": org.slug}
  211. )
  212. with self.feature("organizations:events-v2"):
  213. response = self.client.get(url, format="json")
  214. assert response.status_code == 400, response.content
  215. assert response.data == {"detail": "A valid project must be included."}
  216. def test_multiple_projects_without_global_view(self):
  217. self.store_event(data={"event_id": uuid4().hex}, project_id=self.project.id)
  218. self.store_event(data={"event_id": uuid4().hex}, project_id=self.project2.id)
  219. with self.feature("organizations:events-v2"):
  220. response = self.client.get(self.url, format="json")
  221. assert response.status_code == 400, response.content
  222. assert response.data == {"detail": "You cannot view events from multiple projects."}
  223. def test_project_selected(self):
  224. self.store_event(
  225. data={
  226. "event_id": uuid4().hex,
  227. "timestamp": self.min_ago_iso,
  228. "tags": {"number": "two"},
  229. },
  230. project_id=self.project.id,
  231. )
  232. self.store_event(
  233. data={
  234. "event_id": uuid4().hex,
  235. "timestamp": self.min_ago_iso,
  236. "tags": {"number": "one"},
  237. },
  238. project_id=self.project2.id,
  239. )
  240. with self.feature(self.feature_list):
  241. response = self.client.get(self.url, {"project": [self.project.id]}, format="json")
  242. assert response.status_code == 200, response.content
  243. expected = [{"name": "two", "value": "two", "count": 1}]
  244. self.assert_facet(response, "number", expected)
  245. def test_project_key(self):
  246. self.store_event(
  247. data={
  248. "event_id": uuid4().hex,
  249. "timestamp": self.min_ago_iso,
  250. "tags": {"color": "green"},
  251. },
  252. project_id=self.project.id,
  253. )
  254. self.store_event(
  255. data={
  256. "event_id": uuid4().hex,
  257. "timestamp": self.min_ago_iso,
  258. "tags": {"number": "one"},
  259. },
  260. project_id=self.project2.id,
  261. )
  262. self.store_event(
  263. data={
  264. "event_id": uuid4().hex,
  265. "timestamp": self.min_ago_iso,
  266. "tags": {"color": "green"},
  267. },
  268. project_id=self.project.id,
  269. )
  270. self.store_event(
  271. data={"event_id": uuid4().hex, "timestamp": self.min_ago_iso, "tags": {"color": "red"}},
  272. project_id=self.project.id,
  273. )
  274. with self.feature(self.feature_list):
  275. response = self.client.get(self.url, format="json")
  276. assert response.status_code == 200, response.content
  277. expected = [
  278. {"count": 3, "name": self.project.slug, "value": self.project.slug},
  279. {"count": 1, "name": self.project2.slug, "value": self.project2.slug},
  280. ]
  281. self.assert_facet(response, "project", expected)
  282. def test_malformed_query(self):
  283. self.store_event(data={"event_id": uuid4().hex}, project_id=self.project.id)
  284. self.store_event(data={"event_id": uuid4().hex}, project_id=self.project2.id)
  285. with self.feature(self.feature_list):
  286. response = self.client.get(self.url, format="json", data={"query": "\n\n\n\n"})
  287. assert response.status_code == 400, response.content
  288. assert response.data == {
  289. "detail": "Parse error: 'search' (column 1). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes."
  290. }
  291. def test_environment(self):
  292. self.store_event(
  293. data={
  294. "event_id": uuid4().hex,
  295. "timestamp": self.min_ago_iso,
  296. "tags": {"number": "one"},
  297. "environment": "staging",
  298. },
  299. project_id=self.project2.id,
  300. )
  301. self.store_event(
  302. data={
  303. "event_id": uuid4().hex,
  304. "timestamp": self.min_ago_iso,
  305. "tags": {"number": "one"},
  306. "environment": "production",
  307. },
  308. project_id=self.project.id,
  309. )
  310. self.store_event(
  311. data={
  312. "event_id": uuid4().hex,
  313. "timestamp": self.min_ago_iso,
  314. "tags": {"number": "two"},
  315. },
  316. project_id=self.project.id,
  317. )
  318. with self.feature(self.feature_list):
  319. response = self.client.get(self.url, format="json")
  320. assert response.status_code == 200, response.content
  321. expected = [
  322. {"count": 1, "name": "production", "value": "production"},
  323. {"count": 1, "name": "staging", "value": "staging"},
  324. {"count": 1, "name": None, "value": None},
  325. ]
  326. self.assert_facet(response, "environment", expected)
  327. with self.feature(self.feature_list):
  328. # query by an environment
  329. response = self.client.get(self.url, {"environment": "staging"}, format="json")
  330. assert response.status_code == 200, response.content
  331. expected = [{"count": 1, "name": "staging", "value": "staging"}]
  332. self.assert_facet(response, "environment", expected)
  333. with self.feature(self.feature_list):
  334. # query by multiple environments
  335. response = self.client.get(
  336. self.url, {"environment": ["staging", "production"]}, format="json"
  337. )
  338. assert response.status_code == 200, response.content
  339. expected = [
  340. {"count": 1, "name": "production", "value": "production"},
  341. {"count": 1, "name": "staging", "value": "staging"},
  342. ]
  343. self.assert_facet(response, "environment", expected)
  344. with self.feature(self.feature_list):
  345. # query by multiple environments, including the "no environment" environment
  346. response = self.client.get(
  347. self.url, {"environment": ["staging", "production", ""]}, format="json"
  348. )
  349. assert response.status_code == 200, response.content
  350. expected = [
  351. {"count": 1, "name": "production", "value": "production"},
  352. {"count": 1, "name": "staging", "value": "staging"},
  353. {"count": 1, "name": None, "value": None},
  354. ]
  355. self.assert_facet(response, "environment", expected)
  356. def assert_facet(self, response, key, expected):
  357. actual = None
  358. for facet in response.data:
  359. if facet["key"] == key:
  360. actual = facet
  361. break
  362. assert actual is not None, "Could not find {} facet in {}".format(key, response.data)
  363. assert "topValues" in actual
  364. assert sorted(expected) == sorted(actual["topValues"])