test_organization_global_selection_header.py 19 KB

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