test_organization_global_selection_header.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. from datetime import datetime
  2. from unittest.mock import patch
  3. import pytest
  4. import pytz
  5. from django.utils import timezone
  6. from fixtures.page_objects.issue_details import IssueDetailsPage
  7. from fixtures.page_objects.issue_list import IssueListPage
  8. from sentry.testutils import AcceptanceTestCase, SnubaTestCase
  9. from sentry.testutils.helpers.datetime import before_now, iso_format
  10. from sentry.testutils.silo import region_silo_test
  11. event_time = before_now(days=3).replace(tzinfo=pytz.utc)
  12. @region_silo_test
  13. class OrganizationGlobalHeaderTest(AcceptanceTestCase, SnubaTestCase):
  14. def setUp(self):
  15. super().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=" + str(self.project_1.id)
  64. )
  65. self.browser.wait_until_test_id("awaiting-events")
  66. self.browser.click('[data-test-id="page-filter-project-selector"]')
  67. self.browser.snapshot("globalSelectionHeader - project selector")
  68. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  69. self.browser.snapshot("globalSelectionHeader - environment selector")
  70. self.browser.click('[data-test-id="page-filter-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 f"project={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(self.org.slug, query=f"?project={self.project_2.id}")
  88. assert f"project={self.project_2.id}" in self.browser.current_url
  89. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  90. # reloads page with no project id in URL, selects first project
  91. self.issues_list.visit_issue_list(self.org.slug)
  92. assert f"project={self.project_1.id}" in self.browser.current_url
  93. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  94. # can select a different project
  95. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  96. self.issues_list.wait_until_loaded()
  97. assert f"project={self.project_3.id}" in self.browser.current_url
  98. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  99. # reloading page with no project id in URL after previously
  100. # selecting an explicit project should load previously selected project
  101. # from local storage
  102. # TODO check environment as well
  103. self.issues_list.visit_issue_list(self.org.slug)
  104. self.issues_list.wait_until_loaded()
  105. assert f"project={self.project_3.id}" in self.browser.current_url
  106. def test_global_selection_header_navigates_with_browser_back_button(self):
  107. """
  108. Global Selection Header should:
  109. 1) load project from URL if it exists
  110. 2) enforce a single project if loading issues list with no project in URL
  111. a) last selected project via local storage if it exists
  112. b) otherwise need to just select first project
  113. """
  114. self.create_issues()
  115. # Issues list with project 1 selected
  116. self.issues_list.visit_issue_list(self.org.slug, query="?project=" + str(self.project_1.id))
  117. self.issues_list.visit_issue_list(self.org.slug)
  118. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  119. # selects a different project
  120. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  121. self.issues_list.wait_until_loaded()
  122. assert f"project={self.project_3.id}" in self.browser.current_url
  123. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  124. # simulate pressing the browser back button
  125. self.browser.back()
  126. self.issues_list.wait_until_loaded()
  127. assert f"project={self.project_1.id}" in self.browser.current_url
  128. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  129. def test_global_selection_header_updates_environment_with_browser_navigation_buttons(self):
  130. """
  131. Global Selection Header should:
  132. 1) load project from URL if it exists
  133. 2) clear the current environment if the user clicks clear
  134. 3) reload the environment from URL if it exists on browser navigation
  135. """
  136. with self.feature("organizations:global-views"):
  137. self.create_issues()
  138. """
  139. set up workflow:
  140. 1) environment=All environments
  141. 2) environment=prod
  142. 3) environment=All environments
  143. """
  144. self.issues_list.visit_issue_list(self.org.slug)
  145. self.issues_list.wait_until_loaded()
  146. assert "environment=" not in self.browser.current_url
  147. assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
  148. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  149. self.browser.click('[data-test-id="environment-prod"]')
  150. self.issues_list.wait_until_loaded()
  151. assert "environment=prod" in self.browser.current_url
  152. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  153. # clear environment prod
  154. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  155. self.browser.click('[data-test-id="environment-prod"] input[type="checkbox"]')
  156. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  157. self.issues_list.wait_until_loaded()
  158. assert "environment=" not in self.browser.current_url
  159. assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
  160. """
  161. navigate back through history to the beginning
  162. 1) environment=All Env -> environment=prod
  163. 2) environment=prod -> environment=All Env
  164. """
  165. self.browser.back()
  166. self.issues_list.wait_until_loaded()
  167. assert "environment=prod" in self.browser.current_url
  168. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  169. self.browser.back()
  170. self.issues_list.wait_until_loaded()
  171. assert "environment=" not in self.browser.current_url
  172. assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
  173. """
  174. navigate forward through history to the end
  175. 1) environment=All Env -> environment=prod
  176. 2) environment=prod -> environment=All Env
  177. """
  178. self.browser.forward()
  179. self.issues_list.wait_until_loaded()
  180. assert "environment=prod" in self.browser.current_url
  181. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  182. self.browser.forward()
  183. self.issues_list.wait_until_loaded()
  184. assert "environment=" not in self.browser.current_url
  185. assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
  186. def test_global_selection_header_loads_with_correct_project_with_multi_project(self):
  187. """
  188. Global Selection Header should:
  189. 1) load project from URL if it exists
  190. 2) load last selected projects via local storage if it exists
  191. 3) otherwise can search within "my projects"
  192. """
  193. with self.feature("organizations:global-views"):
  194. self.create_issues()
  195. # No project id in URL, is "my projects"
  196. self.issues_list.visit_issue_list(self.org.slug)
  197. assert "project=" not in self.browser.current_url
  198. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  199. assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
  200. # Uses project id in URL
  201. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  202. assert f"project={self.project_2.id}" in self.browser.current_url
  203. assert (
  204. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  205. )
  206. # should not be in local storage
  207. assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
  208. # reloads page with no project id in URL, remains "My Projects" because
  209. # there has been no explicit project selection via UI
  210. self.issues_list.visit_issue_list(self.org.slug)
  211. assert "project=" not in self.browser.current_url
  212. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  213. # can select a different project
  214. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  215. self.issues_list.wait_until_loaded()
  216. assert f"project={self.project_3.id}" in self.browser.current_url
  217. assert (
  218. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  219. )
  220. self.issues_list.global_selection.select_date("Last 24 hours")
  221. self.issues_list.wait_until_loaded()
  222. assert "statsPeriod=24h" in self.browser.current_url
  223. # This doesn't work because we treat as dynamic data in CI
  224. # assert self.issues_list.global_selection.get_selected_date() == "Last 24 hours"
  225. # lock the filter and then test reloading the page to test persistence
  226. self.issues_list.global_selection.lock_project_filter()
  227. # reloading page with no project id in URL after previously
  228. # selecting an explicit project should load previously selected project
  229. # from local storage
  230. self.issues_list.visit_issue_list(self.org.slug)
  231. self.issues_list.wait_until_loaded()
  232. # TODO check environment as well
  233. assert f"project={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. @patch("django.utils.timezone.now")
  238. def test_issues_list_to_details_and_back_with_all_projects(self, mock_now):
  239. """
  240. If user has access to the `global-views` feature, which allows selecting multiple projects,
  241. they should be able to visit issues list with no project in URL and list issues
  242. for all projects they are members of.
  243. They should also be able to open an issue and then navigate back to still see
  244. "My Projects" in issues list.
  245. """
  246. with self.feature("organizations:global-views"):
  247. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  248. self.create_issues()
  249. self.issues_list.visit_issue_list(self.org.slug)
  250. self.issues_list.wait_for_issue()
  251. assert "project=" not in self.browser.current_url
  252. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  253. # select the issue
  254. self.issues_list.navigate_to_issue(1)
  255. # going back to issues list should not have the issue's project id in url
  256. self.issues_list.issue_details.go_back_to_issues()
  257. self.issues_list.wait_for_issue()
  258. # project id should remain *NOT* in URL
  259. assert "project=" not in self.browser.current_url
  260. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  261. # can select a different project
  262. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  263. self.issues_list.wait_until_loaded()
  264. assert f"project={self.project_3.id}" in self.browser.current_url
  265. assert (
  266. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  267. )
  268. @patch("django.utils.timezone.now")
  269. def test_issues_list_to_details_and_back_with_initial_project(self, mock_now):
  270. """
  271. If user has a project defined in URL, if they visit an issue and then
  272. return back to issues list, that project id should still exist in URL
  273. """
  274. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  275. self.create_issues()
  276. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  277. self.issues_list.wait_for_issue()
  278. assert f"project={self.project_2.id}" in self.browser.current_url
  279. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  280. # select the issue
  281. self.issues_list.navigate_to_issue(1)
  282. # project id should remain in URL
  283. assert f"project={self.project_2.id}" in self.browser.current_url
  284. # going back to issues list should keep project in URL
  285. self.issues_list.issue_details.go_back_to_issues()
  286. self.issues_list.wait_for_issue()
  287. # project id should remain in URL
  288. assert f"project={self.project_2.id}" in self.browser.current_url
  289. # can select a different project
  290. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  291. self.issues_list.wait_until_loaded()
  292. assert f"project={self.project_3.id}" in self.browser.current_url
  293. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  294. @patch("django.utils.timezone.now")
  295. def test_issue_details_to_stream_with_initial_env_no_project(self, mock_now):
  296. """
  297. Visiting issue details directly with no project but with an environment defined in URL.
  298. When navigating back to issues stream, should keep environment and project in context.
  299. """
  300. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  301. self.create_issues()
  302. self.issue_details.visit_issue_in_environment(self.org.slug, self.issue_2.group.id, "prod")
  303. # Make sure issue's project is in URL and in header
  304. assert f"project={self.project_2.id}" in self.browser.current_url
  305. # environment should be in URL and header
  306. assert "environment=prod" in self.browser.current_url
  307. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  308. # going back to issues list should keep project and environment in URL
  309. self.issue_details.go_back_to_issues()
  310. self.issues_list.wait_for_issue()
  311. # project id should remain in URL
  312. assert f"project={self.project_2.id}" in self.browser.current_url
  313. assert "environment=prod" in self.browser.current_url
  314. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  315. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  316. @patch("django.utils.timezone.now")
  317. def test_issue_details_to_stream_with_initial_env_no_project_with_multi_project_feature(
  318. self, mock_now
  319. ):
  320. """
  321. Visiting issue details directly with no project but with an environment defined in URL.
  322. When navigating back to issues stream, should keep environment and project in context.
  323. """
  324. with self.feature("organizations:global-views"):
  325. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  326. self.create_issues()
  327. self.issue_details.visit_issue_in_environment(
  328. self.org.slug, self.issue_2.group.id, "prod"
  329. )
  330. # Make sure issue's project is in URL and in header
  331. assert f"project={self.project_2.id}" in self.browser.current_url
  332. # environment should be in URL and header
  333. assert "environment=prod" in self.browser.current_url
  334. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  335. # can change environment so that when you navigate back to issues stream,
  336. # it keeps environment as selected
  337. # going back to issues list should keep project and environment in URL
  338. self.issue_details.go_back_to_issues()
  339. self.issues_list.wait_for_issue()
  340. # project id should remain in URL
  341. assert f"project={self.project_2.id}" in self.browser.current_url
  342. assert "environment=prod" in self.browser.current_url
  343. assert (
  344. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  345. )
  346. assert self.issue_details.global_selection.get_selected_environment() == "prod"