test_organization_global_selection_header.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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
  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. # Not a part of the current member's teams
  30. self.project_4 = self.create_project(organization=self.org, teams=[], name="Malayan")
  31. self.create_environment(name="development", project=self.project_1)
  32. self.create_environment(name="production", project=self.project_1)
  33. self.create_environment(name="visible", project=self.project_1, is_hidden=False)
  34. self.create_environment(name="not visible", project=self.project_1, is_hidden=True)
  35. self.create_environment(name="dev", project=self.project_2)
  36. self.create_environment(name="prod", project=self.project_2)
  37. self.login_as(self.user)
  38. self.issues_list = IssueListPage(self.browser, self.client)
  39. self.issue_details = IssueDetailsPage(self.browser, self.client)
  40. def create_issues(self):
  41. self.issue_1 = self.store_event(
  42. data={
  43. "event_id": "a" * 32,
  44. "message": "oh no",
  45. "timestamp": iso_format(event_time),
  46. "fingerprint": ["group-1"],
  47. },
  48. project_id=self.project_1.id,
  49. )
  50. self.issue_2 = self.store_event(
  51. data={
  52. "event_id": "b" * 32,
  53. "message": "oh snap",
  54. "timestamp": iso_format(event_time),
  55. "fingerprint": ["group-2"],
  56. "environment": "prod",
  57. },
  58. project_id=self.project_2.id,
  59. )
  60. def test_global_selection_header_dropdown(self):
  61. self.dismiss_assistant()
  62. self.project.update(first_event=django_timezone.now())
  63. self.issues_list.visit_issue_list(
  64. self.org.slug, query="?query=assigned%3Ame&project=" + str(self.project_1.id)
  65. )
  66. self.browser.wait_until_test_id("awaiting-events")
  67. self.browser.click('[data-test-id="page-filter-project-selector"]')
  68. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  69. self.browser.click('[data-test-id="page-filter-timerange-selector"]')
  70. @pytest.mark.skip(reason="Has been flaky lately.")
  71. def test_global_selection_header_loads_with_correct_project(self):
  72. """
  73. Global Selection Header should:
  74. 1) load project from URL if it exists
  75. 2) enforce a single project if loading issues list with no project in URL
  76. a) last selected project via local storage if it exists
  77. b) otherwise need to just select first project
  78. """
  79. self.create_issues()
  80. # No project id in URL, selects first project
  81. self.issues_list.visit_issue_list(self.org.slug)
  82. assert f"project={self.project_1.id}" in self.browser.current_url
  83. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  84. # Uses project id in URL
  85. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  86. assert f"project={self.project_2.id}" in self.browser.current_url
  87. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  88. # reloads page with no project id in URL, selects first project
  89. self.issues_list.visit_issue_list(self.org.slug)
  90. assert f"project={self.project_1.id}" in self.browser.current_url
  91. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  92. # can select a different project
  93. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  94. self.issues_list.wait_until_loaded()
  95. assert f"project={self.project_3.id}" in self.browser.current_url
  96. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  97. # reloading page with no project id in URL after previously
  98. # selecting an explicit project should load previously selected project
  99. # from local storage
  100. # TODO check environment as well
  101. self.issues_list.visit_issue_list(self.org.slug)
  102. self.issues_list.wait_until_loaded()
  103. assert f"project={self.project_3.id}" in self.browser.current_url
  104. def test_global_selection_header_navigates_with_browser_back_button(self):
  105. """
  106. Global Selection Header should:
  107. 1) load project from URL if it exists
  108. 2) enforce a single project if loading issues list with no project in URL
  109. a) last selected project via local storage if it exists
  110. b) otherwise need to just select first project
  111. """
  112. self.create_issues()
  113. # Issues list with project 1 selected
  114. self.issues_list.visit_issue_list(self.org.slug, query="?project=" + str(self.project_1.id))
  115. self.issues_list.visit_issue_list(self.org.slug)
  116. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  117. # selects a different project
  118. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  119. self.issues_list.wait_until_loaded()
  120. assert f"project={self.project_3.id}" in self.browser.current_url
  121. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  122. # simulate pressing the browser back button
  123. self.browser.back()
  124. self.issues_list.wait_until_loaded()
  125. assert f"project={self.project_1.id}" in self.browser.current_url
  126. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
  127. def test_global_selection_header_updates_environment_with_browser_navigation_buttons(self):
  128. """
  129. Global Selection Header should:
  130. 1) load project from URL if it exists
  131. 2) clear the current environment if the user clicks clear
  132. 3) reload the environment from URL if it exists on browser navigation
  133. """
  134. with self.feature("organizations:global-views"):
  135. self.create_issues()
  136. """
  137. set up workflow:
  138. 1) environment=All environments
  139. 2) environment=prod
  140. 3) environment=All environments
  141. """
  142. self.issues_list.visit_issue_list(self.org.slug)
  143. self.issues_list.wait_until_loaded()
  144. assert "environment=" not in self.browser.current_url
  145. assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
  146. self.issues_list.global_selection.select_environment("prod")
  147. self.issues_list.wait_until_loaded()
  148. assert "environment=prod" in self.browser.current_url
  149. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  150. # clear environment prod
  151. self.issues_list.global_selection.open_environment_selector()
  152. clear_path = '//button[@aria-label="Reset" and @role="button"]'
  153. self.browser.wait_until(xpath=clear_path)
  154. button = self.browser.element(xpath=clear_path)
  155. # Use JavaScript to execute click to avoid click intercepted issues
  156. self.browser.driver.execute_script("arguments[0].click()", button)
  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. # reloading page with no project id in URL after previously
  226. # selecting an explicit project should load previously selected project
  227. # from local storage
  228. self.issues_list.visit_issue_list(self.org.slug)
  229. self.issues_list.wait_until_loaded()
  230. # TODO check environment as well
  231. assert f"project={self.project_3.id}" in self.browser.current_url
  232. assert (
  233. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  234. )
  235. @patch("django.utils.timezone.now")
  236. def test_issues_list_to_details_and_back_with_all_projects(self, mock_now):
  237. """
  238. If user has access to the `global-views` feature, which allows selecting multiple projects,
  239. they should be able to visit issues list with no project in URL and list issues
  240. for all projects they are members of.
  241. They should also be able to open an issue and then navigate back to still see
  242. "My Projects" in issues list.
  243. """
  244. with self.feature("organizations:global-views"):
  245. mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
  246. self.create_issues()
  247. self.issues_list.visit_issue_list(self.org.slug)
  248. self.issues_list.wait_for_issue()
  249. assert "project=" not in self.browser.current_url
  250. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  251. # select the issue
  252. self.issues_list.navigate_to_issue(1)
  253. # going back to issues list should not have the issue's project id in url
  254. self.issues_list.issue_details.go_back_to_issues()
  255. self.issues_list.wait_for_issue()
  256. # project id should remain *NOT* in URL
  257. assert "project=" not in self.browser.current_url
  258. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  259. # can select a different project
  260. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  261. self.issues_list.wait_until_loaded()
  262. assert f"project={self.project_3.id}" in self.browser.current_url
  263. assert (
  264. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  265. )
  266. @patch("django.utils.timezone.now")
  267. def test_issues_list_to_details_and_back_with_initial_project(self, mock_now):
  268. """
  269. If user has a project defined in URL, if they visit an issue and then
  270. return back to issues list, that project id should still exist in URL
  271. """
  272. mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
  273. self.create_issues()
  274. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  275. self.issues_list.wait_for_issue()
  276. assert f"project={self.project_2.id}" in self.browser.current_url
  277. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  278. # select the issue
  279. self.issues_list.navigate_to_issue(1)
  280. # project id should remain in URL
  281. assert f"project={self.project_2.id}" in self.browser.current_url
  282. # going back to issues list should keep project in URL
  283. self.issues_list.issue_details.go_back_to_issues()
  284. self.issues_list.wait_for_issue()
  285. # project id should remain in URL
  286. assert f"project={self.project_2.id}" in self.browser.current_url
  287. # can select a different project
  288. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  289. self.issues_list.wait_until_loaded()
  290. assert f"project={self.project_3.id}" in self.browser.current_url
  291. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  292. @patch("django.utils.timezone.now")
  293. def test_issue_details_to_stream_with_initial_env_no_project(self, mock_now):
  294. """
  295. Visiting issue details directly with no project but with an environment defined in URL.
  296. When navigating back to issues stream, should keep environment and project in context.
  297. """
  298. mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
  299. self.create_issues()
  300. self.issue_details.visit_issue_in_environment(self.org.slug, self.issue_2.group.id, "prod")
  301. # Make sure issue's project is in URL and in header
  302. assert f"project={self.project_2.id}" in self.browser.current_url
  303. # environment should be in URL and header
  304. assert "environment=prod" in self.browser.current_url
  305. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  306. # going back to issues list should keep project and environment in URL
  307. self.issue_details.go_back_to_issues()
  308. self.issues_list.wait_for_issue()
  309. # project id should remain in URL
  310. assert f"project={self.project_2.id}" in self.browser.current_url
  311. assert "environment=prod" in self.browser.current_url
  312. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  313. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  314. @patch("django.utils.timezone.now")
  315. def test_issue_details_to_stream_with_initial_env_no_project_with_multi_project_feature(
  316. self, mock_now
  317. ):
  318. """
  319. Visiting issue details directly with no project but with an environment defined in URL.
  320. When navigating back to issues stream, should keep environment and project in context.
  321. """
  322. with self.feature("organizations:global-views"):
  323. mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
  324. self.create_issues()
  325. self.issue_details.visit_issue_in_environment(
  326. self.org.slug, self.issue_2.group.id, "prod"
  327. )
  328. # Make sure issue's project is in URL and in header
  329. assert f"project={self.project_2.id}" in self.browser.current_url
  330. # environment should be in URL and header
  331. assert "environment=prod" in self.browser.current_url
  332. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  333. # can change environment so that when you navigate back to issues stream,
  334. # it keeps environment as selected
  335. # going back to issues list should keep project and environment in URL
  336. self.issue_details.go_back_to_issues()
  337. self.issues_list.wait_for_issue()
  338. # project id should remain in URL
  339. assert f"project={self.project_2.id}" in self.browser.current_url
  340. assert "environment=prod" in self.browser.current_url
  341. assert (
  342. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  343. )
  344. assert self.issue_details.global_selection.get_selected_environment() == "prod"