test_organization_global_selection_header.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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. event_time = before_now(days=3).replace(tzinfo=pytz.utc)
  11. class OrganizationGlobalHeaderTest(AcceptanceTestCase, SnubaTestCase):
  12. def setUp(self):
  13. super().setUp()
  14. self.user = self.create_user("foo@example.com")
  15. self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
  16. self.team = self.create_team(
  17. organization=self.org, name="Mariachi Band", members=[self.user]
  18. )
  19. self.project_1 = self.create_project(
  20. organization=self.org, teams=[self.team], name="Bengal"
  21. )
  22. self.project_2 = self.create_project(
  23. organization=self.org, teams=[self.team], name="Sumatra"
  24. )
  25. self.project_3 = self.create_project(
  26. organization=self.org, teams=[self.team], name="Siberian"
  27. )
  28. self.create_environment(name="development", project=self.project_1)
  29. self.create_environment(name="production", project=self.project_1)
  30. self.create_environment(name="visible", project=self.project_1, is_hidden=False)
  31. self.create_environment(name="not visible", project=self.project_1, is_hidden=True)
  32. self.create_environment(name="dev", project=self.project_2)
  33. self.create_environment(name="prod", project=self.project_2)
  34. self.login_as(self.user)
  35. self.issues_list = IssueListPage(self.browser, self.client)
  36. self.issue_details = IssueDetailsPage(self.browser, self.client)
  37. def create_issues(self):
  38. self.issue_1 = self.store_event(
  39. data={
  40. "event_id": "a" * 32,
  41. "message": "oh no",
  42. "timestamp": iso_format(event_time),
  43. "fingerprint": ["group-1"],
  44. },
  45. project_id=self.project_1.id,
  46. )
  47. self.issue_2 = self.store_event(
  48. data={
  49. "event_id": "b" * 32,
  50. "message": "oh snap",
  51. "timestamp": iso_format(event_time),
  52. "fingerprint": ["group-2"],
  53. "environment": "prod",
  54. },
  55. project_id=self.project_2.id,
  56. )
  57. def test_global_selection_header_dropdown(self):
  58. self.dismiss_assistant()
  59. self.project.update(first_event=timezone.now())
  60. self.issues_list.visit_issue_list(
  61. self.org.slug, query="?query=assigned%3Ame&project=" + str(self.project_1.id)
  62. )
  63. self.browser.wait_until_test_id("awaiting-events")
  64. self.browser.click('[data-test-id="page-filter-project-selector"]')
  65. self.browser.snapshot("globalSelectionHeader - project selector")
  66. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  67. self.browser.snapshot("globalSelectionHeader - environment selector")
  68. self.browser.click('[data-test-id="page-filter-timerange-selector"]')
  69. self.browser.snapshot("globalSelectionHeader - 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 Env"
  146. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  147. self.browser.click('[data-test-id="environment-prod"]')
  148. self.issues_list.wait_until_loaded()
  149. assert "environment=prod" in self.browser.current_url
  150. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  151. # clear environment prod
  152. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  153. self.browser.click('[data-test-id="environment-prod"] [role="checkbox"]')
  154. self.browser.click('[data-test-id="page-filter-environment-selector"]')
  155. self.issues_list.wait_until_loaded()
  156. assert "environment=" not in self.browser.current_url
  157. assert self.issue_details.global_selection.get_selected_environment() == "All Env"
  158. """
  159. navigate back through history to the beginning
  160. 1) environment=All Env -> environment=prod
  161. 2) environment=prod -> environment=All Env
  162. """
  163. self.browser.back()
  164. self.issues_list.wait_until_loaded()
  165. assert "environment=prod" in self.browser.current_url
  166. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  167. self.browser.back()
  168. self.issues_list.wait_until_loaded()
  169. assert "environment=" not in self.browser.current_url
  170. assert self.issue_details.global_selection.get_selected_environment() == "All Env"
  171. """
  172. navigate forward through history to the end
  173. 1) environment=All Env -> environment=prod
  174. 2) environment=prod -> environment=All Env
  175. """
  176. self.browser.forward()
  177. self.issues_list.wait_until_loaded()
  178. assert "environment=prod" in self.browser.current_url
  179. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  180. self.browser.forward()
  181. self.issues_list.wait_until_loaded()
  182. assert "environment=" not in self.browser.current_url
  183. assert self.issue_details.global_selection.get_selected_environment() == "All Env"
  184. def test_global_selection_header_loads_with_correct_project_with_multi_project(self):
  185. """
  186. Global Selection Header should:
  187. 1) load project from URL if it exists
  188. 2) load last selected projects via local storage if it exists
  189. 3) otherwise can search within "my projects"
  190. """
  191. with self.feature("organizations:global-views"):
  192. self.create_issues()
  193. # No project id in URL, is "my projects"
  194. self.issues_list.visit_issue_list(self.org.slug)
  195. assert "project=" not in self.browser.current_url
  196. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  197. assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
  198. # Uses project id in URL
  199. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  200. assert f"project={self.project_2.id}" in self.browser.current_url
  201. assert (
  202. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  203. )
  204. # should not be in local storage
  205. assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
  206. # reloads page with no project id in URL, remains "My Projects" because
  207. # there has been no explicit project selection via UI
  208. self.issues_list.visit_issue_list(self.org.slug)
  209. assert "project=" not in self.browser.current_url
  210. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  211. # can select a different project
  212. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  213. self.issues_list.wait_until_loaded()
  214. assert f"project={self.project_3.id}" in self.browser.current_url
  215. assert (
  216. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  217. )
  218. self.issues_list.global_selection.select_date("Last 24 hours")
  219. self.issues_list.wait_until_loaded()
  220. assert "statsPeriod=24h" in self.browser.current_url
  221. # This doesn't work because we treat as dynamic data in CI
  222. # assert self.issues_list.global_selection.get_selected_date() == "Last 24 hours"
  223. # lock the filter and then test reloading the page to test persistence
  224. self.issues_list.global_selection.lock_project_filter()
  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=pytz.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=pytz.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=pytz.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=pytz.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"