test_organization_global_selection_header.py 19 KB

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