test_organization_global_selection_header.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. from __future__ import absolute_import
  2. from datetime import datetime
  3. import six
  4. import pytz
  5. import pytest
  6. from django.utils import timezone
  7. from sentry.testutils import AcceptanceTestCase, SnubaTestCase
  8. from sentry.testutils.helpers.datetime import iso_format, before_now
  9. from sentry.utils.compat.mock import patch
  10. from tests.acceptance.page_objects.issue_list import IssueListPage
  11. from tests.acceptance.page_objects.issue_details import IssueDetailsPage
  12. event_time = before_now(days=3).replace(tzinfo=pytz.utc)
  13. class OrganizationGlobalHeaderTest(AcceptanceTestCase, SnubaTestCase):
  14. def setUp(self):
  15. super(OrganizationGlobalHeaderTest, self).setUp()
  16. self.user = self.create_user("foo@example.com")
  17. self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
  18. self.team = self.create_team(
  19. organization=self.org, name="Mariachi Band", members=[self.user]
  20. )
  21. self.project_1 = self.create_project(
  22. organization=self.org, teams=[self.team], name="Bengal"
  23. )
  24. self.project_2 = self.create_project(
  25. organization=self.org, teams=[self.team], name="Sumatra"
  26. )
  27. self.project_3 = self.create_project(
  28. organization=self.org, teams=[self.team], name="Siberian"
  29. )
  30. self.create_environment(name="development", project=self.project_1)
  31. self.create_environment(name="production", project=self.project_1)
  32. self.create_environment(name="visible", project=self.project_1, is_hidden=False)
  33. self.create_environment(name="not visible", project=self.project_1, is_hidden=True)
  34. self.create_environment(name="dev", project=self.project_2)
  35. self.create_environment(name="prod", project=self.project_2)
  36. self.login_as(self.user)
  37. self.issues_list = IssueListPage(self.browser, self.client)
  38. self.issue_details = IssueDetailsPage(self.browser, self.client)
  39. def create_issues(self):
  40. self.issue_1 = self.store_event(
  41. data={
  42. "event_id": "a" * 32,
  43. "message": "oh no",
  44. "timestamp": iso_format(event_time),
  45. "fingerprint": ["group-1"],
  46. },
  47. project_id=self.project_1.id,
  48. )
  49. self.issue_2 = self.store_event(
  50. data={
  51. "event_id": "b" * 32,
  52. "message": "oh snap",
  53. "timestamp": iso_format(event_time),
  54. "fingerprint": ["group-2"],
  55. "environment": "prod",
  56. },
  57. project_id=self.project_2.id,
  58. )
  59. def test_global_selection_header_dropdown(self):
  60. self.dismiss_assistant()
  61. self.project.update(first_event=timezone.now())
  62. self.issues_list.visit_issue_list(
  63. self.org.slug, query="?query=assigned%3Ame&project=" + six.text_type(self.project_1.id)
  64. )
  65. self.browser.wait_until_test_id("awaiting-events")
  66. self.browser.click('[data-test-id="global-header-project-selector"]')
  67. self.browser.snapshot("globalSelectionHeader - project selector")
  68. self.browser.click('[data-test-id="global-header-environment-selector"]')
  69. self.browser.snapshot("globalSelectionHeader - environment selector")
  70. self.browser.click('[data-test-id="global-header-timerange-selector"]')
  71. self.browser.snapshot("globalSelectionHeader - timerange selector")
  72. @pytest.mark.skip(reason="Has been flaky lately.")
  73. def test_global_selection_header_loads_with_correct_project(self):
  74. """
  75. Global Selection Header should:
  76. 1) load project from URL if it exists
  77. 2) enforce a single project if loading issues list with no project in URL
  78. a) last selected project via local storage if it exists
  79. b) otherwise need to just select first project
  80. """
  81. self.create_issues()
  82. # No project id in URL, selects first project
  83. self.issues_list.visit_issue_list(self.org.slug)
  84. assert u"project={}".format(self.project_1.id) in self.browser.current_url
  85. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  86. # Uses project id in URL
  87. self.issues_list.visit_issue_list(
  88. self.org.slug, query=u"?project={}".format(self.project_2.id)
  89. )
  90. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  91. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  92. # reloads page with no project id in URL, selects first project
  93. self.issues_list.visit_issue_list(self.org.slug)
  94. assert u"project={}".format(self.project_1.id) in self.browser.current_url
  95. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  96. # can select a different project
  97. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  98. self.issues_list.wait_until_loaded()
  99. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  100. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  101. # reloading page with no project id in URL after previously
  102. # selecting an explicit project should load previously selected project
  103. # from local storage
  104. # TODO check environment as well
  105. self.issues_list.visit_issue_list(self.org.slug)
  106. self.issues_list.wait_until_loaded()
  107. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  108. def test_global_selection_header_navigates_with_browser_back_button(self):
  109. """
  110. Global Selection Header should:
  111. 1) load project from URL if it exists
  112. 2) enforce a single project if loading issues list with no project in URL
  113. a) last selected project via local storage if it exists
  114. b) otherwise need to just select first project
  115. """
  116. self.create_issues()
  117. # Issues list with project 1 selected
  118. self.issues_list.visit_issue_list(
  119. self.org.slug, query="?project=" + six.text_type(self.project_1.id)
  120. )
  121. self.issues_list.visit_issue_list(self.org.slug)
  122. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  123. # selects a different project
  124. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  125. self.issues_list.wait_until_loaded()
  126. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  127. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  128. # simulate pressing the browser back button
  129. self.browser.back()
  130. self.issues_list.wait_until_loaded()
  131. assert u"project={}".format(self.project_1.id) in self.browser.current_url
  132. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  133. def test_global_selection_header_updates_environment_with_browser_navigation_buttons(self):
  134. """
  135. Global Selection Header should:
  136. 1) load project from URL if it exists
  137. 2) clear the current environment if the user clicks clear
  138. 3) reload the environment from URL if it exists on browser navigation
  139. """
  140. with self.feature("organizations:global-views"):
  141. self.create_issues()
  142. """
  143. set up workflow:
  144. 1) environment=All environments
  145. 2) environment=prod
  146. 3) environment=All environments
  147. """
  148. self.issues_list.visit_issue_list(self.org.slug)
  149. self.issues_list.wait_until_loaded()
  150. assert u"environment=" not in self.browser.current_url
  151. assert (
  152. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  153. )
  154. self.browser.click('[data-test-id="global-header-environment-selector"]')
  155. self.browser.click('[data-test-id="environment-prod"]')
  156. self.issues_list.wait_until_loaded()
  157. assert u"environment=prod" in self.browser.current_url
  158. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  159. self.browser.click('[data-test-id="global-header-environment-selector"] > svg')
  160. self.issues_list.wait_until_loaded()
  161. assert u"environment=" not in self.browser.current_url
  162. assert (
  163. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  164. )
  165. """
  166. navigate back through history to the beginning
  167. 1) environment=All Environments -> environment=prod
  168. 2) environment=prod -> environment=All Environments
  169. """
  170. self.browser.back()
  171. self.issues_list.wait_until_loaded()
  172. assert u"environment=prod" in self.browser.current_url
  173. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  174. self.browser.back()
  175. self.issues_list.wait_until_loaded()
  176. assert u"environment=" not in self.browser.current_url
  177. assert (
  178. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  179. )
  180. """
  181. navigate foward through history to the end
  182. 1) environment=All Environments -> environment=prod
  183. 2) environment=prod -> environment=All Environments
  184. """
  185. self.browser.forward()
  186. self.issues_list.wait_until_loaded()
  187. assert u"environment=prod" in self.browser.current_url
  188. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  189. self.browser.forward()
  190. self.issues_list.wait_until_loaded()
  191. assert u"environment=" not in self.browser.current_url
  192. assert (
  193. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  194. )
  195. def test_global_selection_header_loads_with_correct_project_with_multi_project(self):
  196. """
  197. Global Selection Header should:
  198. 1) load project from URL if it exists
  199. 2) load last selected projects via local storage if it exists
  200. 3) otherwise can search within "my projects"
  201. """
  202. with self.feature("organizations:global-views"):
  203. self.create_issues()
  204. # No project id in URL, is "my projects"
  205. self.issues_list.visit_issue_list(self.org.slug)
  206. assert u"project=" not in self.browser.current_url
  207. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  208. assert (
  209. self.browser.get_local_storage_item(u"global-selection:{}".format(self.org.slug))
  210. is None
  211. )
  212. # Uses project id in URL
  213. self.issues_list.visit_issue_list(
  214. self.org.slug, query=u"?project={}".format(self.project_2.id)
  215. )
  216. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  217. assert (
  218. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  219. )
  220. # should not be in local storage
  221. assert (
  222. self.browser.get_local_storage_item(u"global-selection:{}".format(self.org.slug))
  223. is None
  224. )
  225. # reloads page with no project id in URL, remains "My Projects" because
  226. # there has been no explicit project selection via UI
  227. self.issues_list.visit_issue_list(self.org.slug)
  228. assert u"project=" not in self.browser.current_url
  229. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  230. # can select a different project
  231. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  232. self.issues_list.wait_until_loaded()
  233. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  234. assert (
  235. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  236. )
  237. self.issues_list.global_selection.select_date("Last 24 hours")
  238. self.issues_list.wait_until_loaded()
  239. assert u"statsPeriod=24h" in self.browser.current_url
  240. # This doesn't work because we treat as dynamic data in CI
  241. # assert self.issues_list.global_selection.get_selected_date() == "Last 24 hours"
  242. # reloading page with no project id in URL after previously
  243. # selecting an explicit project should load previously selected project
  244. # from local storage
  245. self.issues_list.visit_issue_list(self.org.slug)
  246. self.issues_list.wait_until_loaded()
  247. # TODO check environment as well
  248. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  249. assert (
  250. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  251. )
  252. @patch("django.utils.timezone.now")
  253. def test_issues_list_to_details_and_back_with_all_projects(self, mock_now):
  254. """
  255. If user has access to the `global-views` feature, which allows selecting multiple projects,
  256. they should be able to visit issues list with no project in URL and list issues
  257. for all projects they are members of.
  258. They should also be able to open an issue and then navigate back to still see
  259. "My Projects" in issues list.
  260. """
  261. with self.feature("organizations:global-views"):
  262. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  263. self.create_issues()
  264. self.issues_list.visit_issue_list(self.org.slug)
  265. self.issues_list.wait_for_issue()
  266. assert u"project=" not in self.browser.current_url
  267. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  268. # select the issue
  269. self.issues_list.navigate_to_issue(1)
  270. # going back to issues list should not have the issue's project id in url
  271. self.issues_list.issue_details.go_back_to_issues()
  272. self.issues_list.wait_for_issue()
  273. # project id should remain *NOT* in URL
  274. assert u"project=" not in self.browser.current_url
  275. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  276. # can select a different project
  277. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  278. self.issues_list.wait_until_loaded()
  279. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  280. assert (
  281. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  282. )
  283. @patch("django.utils.timezone.now")
  284. def test_issues_list_to_details_and_back_with_initial_project(self, mock_now):
  285. """
  286. If user has a project defined in URL, if they visit an issue and then
  287. return back to issues list, that project id should still exist in URL
  288. """
  289. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  290. self.create_issues()
  291. self.issues_list.visit_issue_list(
  292. self.org.slug, query=u"?project={}".format(self.project_2.id)
  293. )
  294. self.issues_list.wait_for_issue()
  295. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  296. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  297. # select the issue
  298. self.issues_list.navigate_to_issue(1)
  299. # project id should remain in URL
  300. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  301. # going back to issues list should keep project in URL
  302. self.issues_list.issue_details.go_back_to_issues()
  303. self.issues_list.wait_for_issue()
  304. # project id should remain in URL
  305. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  306. # can select a different project
  307. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  308. self.issues_list.wait_until_loaded()
  309. assert u"project={}".format(self.project_3.id) in self.browser.current_url
  310. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  311. @patch("django.utils.timezone.now")
  312. def test_issue_details_to_stream_with_initial_env_no_project(self, mock_now):
  313. """
  314. Visiting issue details directly with no project but with an environment defined in URL.
  315. When navigating back to issues stream, should keep environment and project in context.
  316. """
  317. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  318. self.create_issues()
  319. self.issue_details.visit_issue_in_environment(self.org.slug, self.issue_2.group.id, "prod")
  320. # Make sure issue's project is in URL and in header
  321. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  322. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  323. # environment should be in URL and header
  324. assert u"environment=prod" in self.browser.current_url
  325. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  326. # going back to issues list should keep project and environment in URL
  327. self.issue_details.go_back_to_issues()
  328. self.issues_list.wait_for_issue()
  329. # project id should remain in URL
  330. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  331. assert u"environment=prod" in self.browser.current_url
  332. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  333. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  334. @patch("django.utils.timezone.now")
  335. def test_issue_details_to_stream_with_initial_env_no_project_with_multi_project_feature(
  336. self, mock_now
  337. ):
  338. """
  339. Visiting issue details directly with no project but with an environment defined in URL.
  340. When navigating back to issues stream, should keep environment and project in context.
  341. """
  342. with self.feature("organizations:global-views"):
  343. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  344. self.create_issues()
  345. self.issue_details.visit_issue_in_environment(
  346. self.org.slug, self.issue_2.group.id, "prod"
  347. )
  348. # Make sure issue's project is in URL and in header
  349. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  350. assert (
  351. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  352. )
  353. # environment should be in URL and header
  354. assert u"environment=prod" in self.browser.current_url
  355. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  356. # can change environment so that when you navigate back to issues stream,
  357. # it keeps environment as selected
  358. # going back to issues list should keep project and environment in URL
  359. self.issue_details.go_back_to_issues()
  360. self.issues_list.wait_for_issue()
  361. # project id should remain in URL
  362. assert u"project={}".format(self.project_2.id) in self.browser.current_url
  363. assert u"environment=prod" in self.browser.current_url
  364. assert (
  365. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  366. )
  367. assert self.issue_details.global_selection.get_selected_environment() == "prod"