test_organization_global_selection_header.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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 sentry.testutils import AcceptanceTestCase, SnubaTestCase
  7. from sentry.testutils.helpers.datetime import before_now, iso_format
  8. from tests.acceptance.page_objects.issue_details import IssueDetailsPage
  9. from tests.acceptance.page_objects.issue_list import IssueListPage
  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="global-header-project-selector"]')
  65. self.browser.snapshot("globalSelectionHeader - project selector")
  66. self.browser.click('[data-test-id="global-header-environment-selector"]')
  67. self.browser.snapshot("globalSelectionHeader - environment selector")
  68. self.browser.click('[data-test-id="global-header-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 (
  146. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  147. )
  148. self.browser.click('[data-test-id="global-header-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. self.browser.click('[data-test-id="global-header-environment-selector"] > svg')
  154. self.issues_list.wait_until_loaded()
  155. assert "environment=" not in self.browser.current_url
  156. assert (
  157. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  158. )
  159. """
  160. navigate back through history to the beginning
  161. 1) environment=All Environments -> environment=prod
  162. 2) environment=prod -> environment=All Environments
  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 (
  172. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  173. )
  174. """
  175. navigate forward through history to the end
  176. 1) environment=All Environments -> environment=prod
  177. 2) environment=prod -> environment=All Environments
  178. """
  179. self.browser.forward()
  180. self.issues_list.wait_until_loaded()
  181. assert "environment=prod" in self.browser.current_url
  182. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  183. self.browser.forward()
  184. self.issues_list.wait_until_loaded()
  185. assert "environment=" not in self.browser.current_url
  186. assert (
  187. self.issue_details.global_selection.get_selected_environment() == "All Environments"
  188. )
  189. def test_global_selection_header_loads_with_correct_project_with_multi_project(self):
  190. """
  191. Global Selection Header should:
  192. 1) load project from URL if it exists
  193. 2) load last selected projects via local storage if it exists
  194. 3) otherwise can search within "my projects"
  195. """
  196. with self.feature("organizations:global-views"):
  197. self.create_issues()
  198. # No project id in URL, is "my projects"
  199. self.issues_list.visit_issue_list(self.org.slug)
  200. assert "project=" not in self.browser.current_url
  201. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  202. assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
  203. # Uses project id in URL
  204. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  205. assert f"project={self.project_2.id}" in self.browser.current_url
  206. assert (
  207. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  208. )
  209. # should not be in local storage
  210. assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
  211. # reloads page with no project id in URL, remains "My Projects" because
  212. # there has been no explicit project selection via UI
  213. self.issues_list.visit_issue_list(self.org.slug)
  214. assert "project=" not in self.browser.current_url
  215. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  216. # can select a different project
  217. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  218. self.issues_list.wait_until_loaded()
  219. assert f"project={self.project_3.id}" in self.browser.current_url
  220. assert (
  221. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  222. )
  223. self.issues_list.global_selection.select_date("Last 24 hours")
  224. self.issues_list.wait_until_loaded()
  225. assert "statsPeriod=24h" in self.browser.current_url
  226. # This doesn't work because we treat as dynamic data in CI
  227. # assert self.issues_list.global_selection.get_selected_date() == "Last 24 hours"
  228. # reloading page with no project id in URL after previously
  229. # selecting an explicit project should load previously selected project
  230. # from local storage
  231. self.issues_list.visit_issue_list(self.org.slug)
  232. self.issues_list.wait_until_loaded()
  233. # TODO check environment as well
  234. assert f"project={self.project_3.id}" in self.browser.current_url
  235. assert (
  236. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  237. )
  238. @patch("django.utils.timezone.now")
  239. def test_issues_list_to_details_and_back_with_all_projects(self, mock_now):
  240. """
  241. If user has access to the `global-views` feature, which allows selecting multiple projects,
  242. they should be able to visit issues list with no project in URL and list issues
  243. for all projects they are members of.
  244. They should also be able to open an issue and then navigate back to still see
  245. "My Projects" in issues list.
  246. """
  247. with self.feature("organizations:global-views"):
  248. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  249. self.create_issues()
  250. self.issues_list.visit_issue_list(self.org.slug)
  251. self.issues_list.wait_for_issue()
  252. assert "project=" not in self.browser.current_url
  253. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  254. # select the issue
  255. self.issues_list.navigate_to_issue(1)
  256. # going back to issues list should not have the issue's project id in url
  257. self.issues_list.issue_details.go_back_to_issues()
  258. self.issues_list.wait_for_issue()
  259. # project id should remain *NOT* in URL
  260. assert "project=" not in self.browser.current_url
  261. assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
  262. # can select a different project
  263. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  264. self.issues_list.wait_until_loaded()
  265. assert f"project={self.project_3.id}" in self.browser.current_url
  266. assert (
  267. self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  268. )
  269. @patch("django.utils.timezone.now")
  270. def test_issues_list_to_details_and_back_with_initial_project(self, mock_now):
  271. """
  272. If user has a project defined in URL, if they visit an issue and then
  273. return back to issues list, that project id should still exist in URL
  274. """
  275. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  276. self.create_issues()
  277. self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
  278. self.issues_list.wait_for_issue()
  279. assert f"project={self.project_2.id}" in self.browser.current_url
  280. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  281. # select the issue
  282. self.issues_list.navigate_to_issue(1)
  283. # project id should remain in URL
  284. assert f"project={self.project_2.id}" in self.browser.current_url
  285. # going back to issues list should keep project in URL
  286. self.issues_list.issue_details.go_back_to_issues()
  287. self.issues_list.wait_for_issue()
  288. # project id should remain in URL
  289. assert f"project={self.project_2.id}" in self.browser.current_url
  290. # can select a different project
  291. self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
  292. self.issues_list.wait_until_loaded()
  293. assert f"project={self.project_3.id}" in self.browser.current_url
  294. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
  295. @patch("django.utils.timezone.now")
  296. def test_issue_details_to_stream_with_initial_env_no_project(self, mock_now):
  297. """
  298. Visiting issue details directly with no project but with an environment defined in URL.
  299. When navigating back to issues stream, should keep environment and project in context.
  300. """
  301. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  302. self.create_issues()
  303. self.issue_details.visit_issue_in_environment(self.org.slug, self.issue_2.group.id, "prod")
  304. # Make sure issue's project is in URL and in header
  305. assert f"project={self.project_2.id}" in self.browser.current_url
  306. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  307. # environment should be in URL and header
  308. assert "environment=prod" in self.browser.current_url
  309. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  310. # going back to issues list should keep project and environment in URL
  311. self.issue_details.go_back_to_issues()
  312. self.issues_list.wait_for_issue()
  313. # project id should remain in URL
  314. assert f"project={self.project_2.id}" in self.browser.current_url
  315. assert "environment=prod" in self.browser.current_url
  316. assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  317. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  318. @patch("django.utils.timezone.now")
  319. def test_issue_details_to_stream_with_initial_env_no_project_with_multi_project_feature(
  320. self, mock_now
  321. ):
  322. """
  323. Visiting issue details directly with no project but with an environment defined in URL.
  324. When navigating back to issues stream, should keep environment and project in context.
  325. """
  326. with self.feature("organizations:global-views"):
  327. mock_now.return_value = datetime.utcnow().replace(tzinfo=pytz.utc)
  328. self.create_issues()
  329. self.issue_details.visit_issue_in_environment(
  330. self.org.slug, self.issue_2.group.id, "prod"
  331. )
  332. # Make sure issue's project is in URL and in header
  333. assert f"project={self.project_2.id}" in self.browser.current_url
  334. assert (
  335. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  336. )
  337. # environment should be in URL and header
  338. assert "environment=prod" in self.browser.current_url
  339. assert self.issue_details.global_selection.get_selected_environment() == "prod"
  340. # can change environment so that when you navigate back to issues stream,
  341. # it keeps environment as selected
  342. # going back to issues list should keep project and environment in URL
  343. self.issue_details.go_back_to_issues()
  344. self.issues_list.wait_for_issue()
  345. # project id should remain in URL
  346. assert f"project={self.project_2.id}" in self.browser.current_url
  347. assert "environment=prod" in self.browser.current_url
  348. assert (
  349. self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
  350. )
  351. assert self.issue_details.global_selection.get_selected_environment() == "prod"