123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- from datetime import datetime, timezone
- from unittest.mock import patch
- import pytest
- from django.utils import timezone as django_timezone
- from fixtures.page_objects.issue_details import IssueDetailsPage
- from fixtures.page_objects.issue_list import IssueListPage
- from sentry.testutils.cases import AcceptanceTestCase, SnubaTestCase
- from sentry.testutils.helpers.datetime import before_now, iso_format
- from sentry.testutils.silo import no_silo_test
- event_time = before_now(days=3).replace(tzinfo=timezone.utc)
- @no_silo_test
- class OrganizationGlobalHeaderTest(AcceptanceTestCase, SnubaTestCase):
- def setUp(self):
- super().setUp()
- self.user = self.create_user("foo@example.com")
- self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
- self.team = self.create_team(
- organization=self.org, name="Mariachi Band", members=[self.user]
- )
- self.project_1 = self.create_project(
- organization=self.org, teams=[self.team], name="Bengal"
- )
- self.project_2 = self.create_project(
- organization=self.org, teams=[self.team], name="Sumatra"
- )
- self.project_3 = self.create_project(
- organization=self.org, teams=[self.team], name="Siberian"
- )
- # Not a part of the current member's teams
- self.project_4 = self.create_project(organization=self.org, teams=[], name="Malayan")
- self.create_environment(name="development", project=self.project_1)
- self.create_environment(name="production", project=self.project_1)
- self.create_environment(name="visible", project=self.project_1, is_hidden=False)
- self.create_environment(name="not visible", project=self.project_1, is_hidden=True)
- self.create_environment(name="dev", project=self.project_2)
- self.create_environment(name="prod", project=self.project_2)
- self.login_as(self.user)
- self.issues_list = IssueListPage(self.browser, self.client)
- self.issue_details = IssueDetailsPage(self.browser, self.client)
- def create_issues(self):
- self.issue_1 = self.store_event(
- data={
- "event_id": "a" * 32,
- "message": "oh no",
- "timestamp": iso_format(event_time),
- "fingerprint": ["group-1"],
- },
- project_id=self.project_1.id,
- )
- self.issue_2 = self.store_event(
- data={
- "event_id": "b" * 32,
- "message": "oh snap",
- "timestamp": iso_format(event_time),
- "fingerprint": ["group-2"],
- "environment": "prod",
- },
- project_id=self.project_2.id,
- )
- def test_global_selection_header_dropdown(self):
- self.dismiss_assistant()
- self.project.update(first_event=django_timezone.now())
- self.issues_list.visit_issue_list(
- self.org.slug, query="?query=assigned%3Ame&project=" + str(self.project_1.id)
- )
- self.browser.wait_until_test_id("awaiting-events")
- self.browser.click('[data-test-id="page-filter-project-selector"]')
- self.browser.click('[data-test-id="page-filter-environment-selector"]')
- self.browser.click('[data-test-id="page-filter-timerange-selector"]')
- @pytest.mark.skip(reason="Has been flaky lately.")
- def test_global_selection_header_loads_with_correct_project(self):
- """
- Global Selection Header should:
- 1) load project from URL if it exists
- 2) enforce a single project if loading issues list with no project in URL
- a) last selected project via local storage if it exists
- b) otherwise need to just select first project
- """
- self.create_issues()
- # No project id in URL, selects first project
- self.issues_list.visit_issue_list(self.org.slug)
- assert f"project={self.project_1.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
- # Uses project id in URL
- self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
- assert f"project={self.project_2.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
- # reloads page with no project id in URL, selects first project
- self.issues_list.visit_issue_list(self.org.slug)
- assert f"project={self.project_1.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
- # can select a different project
- self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_3.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
- # reloading page with no project id in URL after previously
- # selecting an explicit project should load previously selected project
- # from local storage
- # TODO check environment as well
- self.issues_list.visit_issue_list(self.org.slug)
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_3.id}" in self.browser.current_url
- def test_global_selection_header_navigates_with_browser_back_button(self):
- """
- Global Selection Header should:
- 1) load project from URL if it exists
- 2) enforce a single project if loading issues list with no project in URL
- a) last selected project via local storage if it exists
- b) otherwise need to just select first project
- """
- self.create_issues()
- # Issues list with project 1 selected
- self.issues_list.visit_issue_list(self.org.slug, query="?project=" + str(self.project_1.id))
- self.issues_list.visit_issue_list(self.org.slug)
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
- # selects a different project
- self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_3.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
- # simulate pressing the browser back button
- self.browser.back()
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_1.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_1.slug
- def test_global_selection_header_updates_environment_with_browser_navigation_buttons(self):
- """
- Global Selection Header should:
- 1) load project from URL if it exists
- 2) clear the current environment if the user clicks clear
- 3) reload the environment from URL if it exists on browser navigation
- """
- with self.feature("organizations:global-views"):
- self.create_issues()
- """
- set up workflow:
- 1) environment=All environments
- 2) environment=prod
- 3) environment=All environments
- """
- self.issues_list.visit_issue_list(self.org.slug)
- self.issues_list.wait_until_loaded()
- assert "environment=" not in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
- self.issues_list.global_selection.select_environment("prod")
- self.issues_list.wait_until_loaded()
- assert "environment=prod" in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
- # clear environment prod
- self.issues_list.global_selection.open_environment_selector()
- clear_path = '//button[@aria-label="Reset" and @role="button"]'
- self.browser.wait_until(xpath=clear_path)
- button = self.browser.element(xpath=clear_path)
- # Use JavaScript to execute click to avoid click intercepted issues
- self.browser.driver.execute_script("arguments[0].click()", button)
- self.issues_list.wait_until_loaded()
- assert "environment=" not in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
- """
- navigate back through history to the beginning
- 1) environment=All Env -> environment=prod
- 2) environment=prod -> environment=All Env
- """
- self.browser.back()
- self.issues_list.wait_until_loaded()
- assert "environment=prod" in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
- self.browser.back()
- self.issues_list.wait_until_loaded()
- assert "environment=" not in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
- """
- navigate forward through history to the end
- 1) environment=All Env -> environment=prod
- 2) environment=prod -> environment=All Env
- """
- self.browser.forward()
- self.issues_list.wait_until_loaded()
- assert "environment=prod" in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
- self.browser.forward()
- self.issues_list.wait_until_loaded()
- assert "environment=" not in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "All Envs"
- def test_global_selection_header_loads_with_correct_project_with_multi_project(self):
- """
- Global Selection Header should:
- 1) load project from URL if it exists
- 2) load last selected projects via local storage if it exists
- 3) otherwise can search within "my projects"
- """
- with self.feature("organizations:global-views"):
- self.create_issues()
- # No project id in URL, is "my projects"
- self.issues_list.visit_issue_list(self.org.slug)
- assert "project=" not in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
- assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
- # Uses project id in URL
- self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
- assert f"project={self.project_2.id}" in self.browser.current_url
- assert (
- self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
- )
- # should not be in local storage
- assert self.browser.get_local_storage_item(f"global-selection:{self.org.slug}") is None
- # reloads page with no project id in URL, remains "My Projects" because
- # there has been no explicit project selection via UI
- self.issues_list.visit_issue_list(self.org.slug)
- assert "project=" not in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
- # can select a different project
- self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_3.id}" in self.browser.current_url
- assert (
- self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
- )
- self.issues_list.global_selection.select_date("Last 24 hours")
- self.issues_list.wait_until_loaded()
- assert "statsPeriod=24h" in self.browser.current_url
- # This doesn't work because we treat as dynamic data in CI
- # assert self.issues_list.global_selection.get_selected_date() == "Last 24 hours"
- # reloading page with no project id in URL after previously
- # selecting an explicit project should load previously selected project
- # from local storage
- self.issues_list.visit_issue_list(self.org.slug)
- self.issues_list.wait_until_loaded()
- # TODO check environment as well
- assert f"project={self.project_3.id}" in self.browser.current_url
- assert (
- self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
- )
- @patch("django.utils.timezone.now")
- def test_issues_list_to_details_and_back_with_all_projects(self, mock_now):
- """
- If user has access to the `global-views` feature, which allows selecting multiple projects,
- they should be able to visit issues list with no project in URL and list issues
- for all projects they are members of.
- They should also be able to open an issue and then navigate back to still see
- "My Projects" in issues list.
- """
- with self.feature("organizations:global-views"):
- mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
- self.create_issues()
- self.issues_list.visit_issue_list(self.org.slug)
- self.issues_list.wait_for_issue()
- assert "project=" not in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
- # select the issue
- self.issues_list.navigate_to_issue(1)
- # going back to issues list should not have the issue's project id in url
- self.issues_list.issue_details.go_back_to_issues()
- self.issues_list.wait_for_issue()
- # project id should remain *NOT* in URL
- assert "project=" not in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == "My Projects"
- # can select a different project
- self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_3.id}" in self.browser.current_url
- assert (
- self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
- )
- @patch("django.utils.timezone.now")
- def test_issues_list_to_details_and_back_with_initial_project(self, mock_now):
- """
- If user has a project defined in URL, if they visit an issue and then
- return back to issues list, that project id should still exist in URL
- """
- mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
- self.create_issues()
- self.issues_list.visit_issue_list(self.org.slug, query=f"?project={self.project_2.id}")
- self.issues_list.wait_for_issue()
- assert f"project={self.project_2.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
- # select the issue
- self.issues_list.navigate_to_issue(1)
- # project id should remain in URL
- assert f"project={self.project_2.id}" in self.browser.current_url
- # going back to issues list should keep project in URL
- self.issues_list.issue_details.go_back_to_issues()
- self.issues_list.wait_for_issue()
- # project id should remain in URL
- assert f"project={self.project_2.id}" in self.browser.current_url
- # can select a different project
- self.issues_list.global_selection.select_project_by_slug(self.project_3.slug)
- self.issues_list.wait_until_loaded()
- assert f"project={self.project_3.id}" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_3.slug
- @patch("django.utils.timezone.now")
- def test_issue_details_to_stream_with_initial_env_no_project(self, mock_now):
- """
- Visiting issue details directly with no project but with an environment defined in URL.
- When navigating back to issues stream, should keep environment and project in context.
- """
- mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
- self.create_issues()
- self.issue_details.visit_issue_in_environment(self.org.slug, self.issue_2.group.id, "prod")
- # Make sure issue's project is in URL and in header
- assert f"project={self.project_2.id}" in self.browser.current_url
- # environment should be in URL and header
- assert "environment=prod" in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
- # going back to issues list should keep project and environment in URL
- self.issue_details.go_back_to_issues()
- self.issues_list.wait_for_issue()
- # project id should remain in URL
- assert f"project={self.project_2.id}" in self.browser.current_url
- assert "environment=prod" in self.browser.current_url
- assert self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
- @patch("django.utils.timezone.now")
- def test_issue_details_to_stream_with_initial_env_no_project_with_multi_project_feature(
- self, mock_now
- ):
- """
- Visiting issue details directly with no project but with an environment defined in URL.
- When navigating back to issues stream, should keep environment and project in context.
- """
- with self.feature("organizations:global-views"):
- mock_now.return_value = datetime.utcnow().replace(tzinfo=timezone.utc)
- self.create_issues()
- self.issue_details.visit_issue_in_environment(
- self.org.slug, self.issue_2.group.id, "prod"
- )
- # Make sure issue's project is in URL and in header
- assert f"project={self.project_2.id}" in self.browser.current_url
- # environment should be in URL and header
- assert "environment=prod" in self.browser.current_url
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
- # can change environment so that when you navigate back to issues stream,
- # it keeps environment as selected
- # going back to issues list should keep project and environment in URL
- self.issue_details.go_back_to_issues()
- self.issues_list.wait_for_issue()
- # project id should remain in URL
- assert f"project={self.project_2.id}" in self.browser.current_url
- assert "environment=prod" in self.browser.current_url
- assert (
- self.issues_list.global_selection.get_selected_project_slug() == self.project_2.slug
- )
- assert self.issue_details.global_selection.get_selected_environment() == "prod"
|