123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935 |
- from selenium.webdriver.common.action_chains import ActionChains
- from selenium.webdriver.common.by import By
- from selenium.webdriver.common.keys import Keys
- from selenium.webdriver.support import expected_conditions as EC
- from selenium.webdriver.support.wait import WebDriverWait
- from fixtures.page_objects.dashboard_detail import (
- EDIT_WIDGET_BUTTON,
- WIDGET_DRAG_HANDLE,
- WIDGET_RESIZE_HANDLE,
- WIDGET_TITLE_FIELD,
- DashboardDetailPage,
- )
- from sentry.models import (
- Dashboard,
- DashboardWidget,
- DashboardWidgetDisplayTypes,
- DashboardWidgetQuery,
- DashboardWidgetTypes,
- )
- from sentry.testutils import AcceptanceTestCase
- from sentry.testutils.helpers.datetime import before_now, iso_format
- FEATURE_NAMES = [
- "organizations:discover-basic",
- "organizations:discover-query",
- "organizations:dashboards-basic",
- ]
- EDIT_FEATURE = ["organizations:dashboards-edit"]
- GRID_LAYOUT_FEATURE = ["organizations:dashboard-grid-layout"]
- WIDGET_LIBRARY_FEATURE = ["organizations:widget-library"]
- class OrganizationDashboardsAcceptanceTest(AcceptanceTestCase):
- def setUp(self):
- super().setUp()
- min_ago = iso_format(before_now(minutes=1))
- self.store_event(
- data={"event_id": "a" * 32, "message": "oh no", "timestamp": min_ago},
- project_id=self.project.id,
- )
- self.dashboard = Dashboard.objects.create(
- title="Dashboard 1", created_by=self.user, organization=self.organization
- )
- self.existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Existing Widget",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=self.existing_widget,
- fields=["count()"],
- columns=[],
- aggregates=["count()"],
- order=0,
- )
- self.page = DashboardDetailPage(
- self.browser, self.client, organization=self.organization, dashboard=self.dashboard
- )
- self.login_as(self.user)
- def test_view_dashboard(self):
- with self.feature(FEATURE_NAMES):
- self.page.visit_default_overview()
- self.browser.snapshot("dashboards - default overview")
- def test_view_dashboard_with_manager(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_default_overview()
- self.browser.snapshot("dashboards - default overview manager")
- def test_edit_dashboard(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_default_overview()
- self.page.enter_edit_state()
- self.browser.snapshot("dashboards - edit state")
- def test_add_widget(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_default_overview()
- self.page.enter_edit_state()
- # Add a widget
- self.page.click_dashboard_add_widget_button()
- self.browser.snapshot("dashboards - add widget")
- def test_edit_widget(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_default_overview()
- self.page.enter_edit_state()
- # Edit the first widget.
- button = self.browser.element(EDIT_WIDGET_BUTTON)
- button.click()
- self.browser.snapshot("dashboards - edit widget")
- def test_widget_library(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + WIDGET_LIBRARY_FEATURE):
- self.page.visit_default_overview()
- # Open widget library
- self.page.click_dashboard_header_add_widget_button()
- self.browser.element('[data-test-id="library-tab"]').click()
- # Select/deselect widget library cards
- self.browser.element('[data-test-id="widget-library-card-0"]').click()
- self.browser.element('[data-test-id="widget-library-card-2"]').click()
- self.browser.element('[data-test-id="widget-library-card-3"]').click()
- self.browser.element('[data-test-id="widget-library-card-2"]').click()
- self.browser.snapshot("dashboards - widget library")
- def test_duplicate_widget_in_view_mode(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_dashboard_detail()
- self.browser.element('[aria-haspopup="true"]').click()
- self.browser.element('[data-test-id="duplicate-widget"]').click()
- self.page.wait_until_loaded()
- self.browser.element('[aria-haspopup="true"]').click()
- self.browser.element('[data-test-id="duplicate-widget"]').click()
- self.page.wait_until_loaded()
- self.browser.snapshot("dashboard widget - duplicate")
- def test_delete_widget_in_view_mode(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_dashboard_detail()
- self.browser.element('[aria-haspopup="true"]').click()
- self.browser.element('[data-test-id="delete-widget"]').click()
- self.browser.element('[data-test-id="confirm-button"]').click()
- self.page.wait_until_loaded()
- self.browser.snapshot("dashboard widget - delete")
- class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
- def setUp(self):
- super().setUp()
- min_ago = iso_format(before_now(minutes=1))
- self.store_event(
- data={"event_id": "a" * 32, "message": "oh no", "timestamp": min_ago},
- project_id=self.project.id,
- )
- self.dashboard = Dashboard.objects.create(
- title="Dashboard 1", created_by=self.user, organization=self.organization
- )
- self.page = DashboardDetailPage(
- self.browser, self.client, organization=self.organization, dashboard=self.dashboard
- )
- self.login_as(self.user)
- def capture_screenshots(self, screenshot_name):
- """
- Captures screenshots in both a pre and post refresh state.
- Necessary for verifying that the layout persists after saving.
- """
- self.page.wait_until_loaded()
- self.browser.snapshot(screenshot_name)
- self.browser.refresh()
- self.page.wait_until_loaded()
- self.browser.snapshot(f"{screenshot_name} (refresh)")
- def test_default_overview_dashboard_layout(self):
- with self.feature(FEATURE_NAMES + GRID_LAYOUT_FEATURE):
- self.page.visit_default_overview()
- self.browser.snapshot("dashboards - default overview layout")
- def test_add_and_move_new_widget_on_existing_dashboard(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- self.page.add_widget_through_dashboard("New Widget")
- # Drag to the right
- dragHandle = self.browser.element(WIDGET_DRAG_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, 1000, 0).perform()
- self.page.save_dashboard()
- self.capture_screenshots("dashboards - save new widget layout in custom dashboard")
- def test_create_new_dashboard_with_modified_widget_layout(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- # Create a new dashboard
- self.page.visit_create_dashboard()
- self.page.add_widget_through_dashboard("New Widget")
- # Drag to the right
- dragHandle = self.browser.element(WIDGET_DRAG_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, 1000, 0).perform()
- self.page.save_dashboard()
- # Wait for page redirect, or else loading check passes too early
- wait = WebDriverWait(self.browser.driver, 10)
- wait.until(
- lambda driver: (
- f"/organizations/{self.organization.slug}/dashboards/new/"
- not in driver.current_url
- )
- )
- self.capture_screenshots("dashboards - save widget layout in new custom dashboard")
- def test_move_existing_widget_on_existing_dashboard(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Existing Widget",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- # Drag to the right
- dragHandle = self.browser.element(WIDGET_DRAG_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, 1000, 0).perform()
- self.page.save_dashboard()
- self.capture_screenshots("dashboards - move existing widget on existing dashboard")
- def test_add_by_widget_library_do_not_overlap(self):
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + WIDGET_LIBRARY_FEATURE + GRID_LAYOUT_FEATURE
- ):
- self.page.visit_dashboard_detail()
- self.page.click_dashboard_header_add_widget_button()
- self.browser.element('[data-test-id="library-tab"]').click()
- # Add library widgets
- self.browser.element('[data-test-id="widget-library-card-0"]').click()
- self.browser.element('[data-test-id="widget-library-card-2"]').click()
- self.browser.element('[data-test-id="widget-library-card-3"]').click()
- self.browser.element('[data-test-id="widget-library-card-2"]').click()
- self.browser.element('[data-test-id="confirm-widgets"]').click()
- self.capture_screenshots(
- "dashboards - widgets from widget library do not overlap when added"
- )
- def test_widget_edit_keeps_same_layout_after_modification(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Existing Widget",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- # Drag existing widget to the right
- dragHandle = self.browser.element(WIDGET_DRAG_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, 1000, 0).perform()
- # Edit the existing widget
- button = self.browser.element(EDIT_WIDGET_BUTTON)
- button.click()
- title_input = self.browser.element(WIDGET_TITLE_FIELD)
- title_input.clear()
- title_input.send_keys(Keys.END, "Existing WidgetUPDATED!!")
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- # Add and drag new widget to the right
- self.page.add_widget_through_dashboard("New Widget")
- dragHandle = self.browser.element(
- f".react-grid-item:nth-of-type(2) {WIDGET_DRAG_HANDLE}"
- )
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, 1000, 0)
- action.perform()
- # Edit the new widget
- button = self.browser.element(f".react-grid-item:nth-of-type(2) {EDIT_WIDGET_BUTTON}")
- button.click()
- title_input = self.browser.element(WIDGET_TITLE_FIELD)
- title_input.clear()
- title_input.send_keys(Keys.END, "New WidgetUPDATED!!")
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- self.page.save_dashboard()
- self.capture_screenshots(
- "dashboards - edit widgets after layout change does not reset layout"
- )
- def test_add_issue_widgets_do_not_overlap(self):
- def add_issue_widget(widget_title):
- self.browser.wait_until_clickable('[data-test-id="widget-add"]')
- self.page.click_dashboard_add_widget_button()
- title_input = self.browser.element(WIDGET_TITLE_FIELD)
- title_input.send_keys(widget_title)
- self.browser.element(
- '[aria-label="Select Issues (States, Assignment, Time, etc.)"]'
- ).click()
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- add_issue_widget("Issue Widget 1")
- add_issue_widget("Issue Widget 2")
- self.page.save_dashboard()
- self.capture_screenshots("dashboards - issue widgets do not overlap")
- def test_resize_new_and_existing_widgets(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Existing Widget",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- # Resize existing widget
- resizeHandle = self.browser.element(WIDGET_RESIZE_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 500, 0).perform()
- self.page.add_widget_through_dashboard("New Widget")
- # Drag it to the left for consistency
- dragHandle = self.browser.element(
- f".react-grid-item:nth-of-type(2) {WIDGET_DRAG_HANDLE}"
- )
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, -1000, 0).perform()
- # Resize new widget, get the 2nd element instead of the "last" because the "last" is
- # the add widget button
- resizeHandle = self.browser.element(
- f".react-grid-item:nth-of-type(2) {WIDGET_RESIZE_HANDLE}"
- )
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 500, 0).perform()
- self.page.save_dashboard()
- self.capture_screenshots("dashboards - resize new and existing widgets")
- def test_delete_existing_widget_does_not_trigger_new_widget_layout_reset(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Existing Widget",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- self.page.add_widget_through_dashboard("New Widget")
- # Drag it to the bottom left
- dragHandle = self.browser.element(
- f".react-grid-item:nth-of-type(2) {WIDGET_DRAG_HANDLE}"
- )
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(dragHandle, -500, 500).perform()
- # Resize new widget, get the 2nd element instead of the "last" because the "last" is
- # the add widget button
- resizeHandle = self.browser.element(
- f".react-grid-item:nth-of-type(2) {WIDGET_RESIZE_HANDLE}"
- )
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 500, 0).perform()
- # Delete first existing widget
- delete_widget_button = self.browser.element(
- '.react-grid-item:first-of-type [data-test-id="widget-delete"]'
- )
- delete_widget_button.click()
- self.page.save_dashboard()
- self.capture_screenshots(
- "dashboards - delete existing widget does not reset new widget layout"
- )
- def test_resize_big_number_widget(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Big Number Widget",
- display_type=DashboardWidgetDisplayTypes.BIG_NUMBER,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget,
- fields=["count_unique(issue)"],
- columns=[],
- aggregates=["count_unique(issue)"],
- order=0,
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- # Resize existing widget
- resizeHandle = self.browser.element(WIDGET_RESIZE_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 200, 200).perform()
- self.page.save_dashboard()
- self.capture_screenshots("dashboards - resize big number widget")
- def test_default_layout_when_widgets_do_not_have_layout_set(self):
- existing_widgets = DashboardWidget.objects.bulk_create(
- [
- DashboardWidget(
- dashboard=self.dashboard,
- order=i,
- title=f"Existing Widget {i}",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- for i in range(4)
- ]
- )
- DashboardWidgetQuery.objects.bulk_create(
- [
- DashboardWidgetQuery(
- widget=existing_widget,
- fields=["count()"],
- columns=[],
- aggregates=["count()"],
- order=0,
- )
- for existing_widget in existing_widgets
- ]
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.page.visit_dashboard_detail()
- self.page.wait_until_loaded()
- self.browser.snapshot("dashboards - default layout when widgets do not have layout set")
- def test_duplicate_widget_in_view_mode(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Big Number Widget",
- display_type=DashboardWidgetDisplayTypes.BIG_NUMBER,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget,
- fields=["count_unique(issue)"],
- columns=[],
- aggregates=["count_unique(issue)"],
- order=0,
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_dashboard_detail()
- self.browser.element('[aria-haspopup="true"]').click()
- self.browser.element('[data-test-id="duplicate-widget"]').click()
- self.page.wait_until_loaded()
- self.browser.element('[aria-haspopup="true"]').click()
- self.browser.element('[data-test-id="duplicate-widget"]').click()
- self.page.wait_until_loaded()
- # Should not trigger alert
- self.page.enter_edit_state()
- self.page.click_cancel_button()
- wait = WebDriverWait(self.browser.driver, 5)
- wait.until_not(EC.alert_is_present())
- self.browser.snapshot("dashboard widget - duplicate with grid")
- def test_delete_widget_in_view_mode(self):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Big Number Widget",
- display_type=DashboardWidgetDisplayTypes.BIG_NUMBER,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget,
- fields=["count_unique(issue)"],
- columns=[],
- aggregates=["count_unique(issue)"],
- order=0,
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.page.visit_dashboard_detail()
- self.browser.element('[aria-haspopup="true"]').click()
- self.browser.element('[data-test-id="delete-widget"]').click()
- self.browser.element('[data-test-id="confirm-button"]').click()
- self.page.wait_until_loaded()
- self.browser.snapshot("dashboard widget - delete with grid")
- def test_cancel_without_changes_does_not_trigger_confirm_with_widget_library_through_header(
- self,
- ):
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- # Open widget library
- self.page.click_dashboard_header_add_widget_button()
- self.browser.element('[data-test-id="library-tab"]').click()
- # Select/deselect widget library cards
- self.browser.element('[data-test-id="widget-library-card-0"]').click()
- self.browser.element('[data-test-id="widget-library-card-2"]').click()
- # Save widget library selections
- button = self.browser.element('[data-test-id="confirm-widgets"]')
- button.click()
- self.page.wait_until_loaded()
- # Should not trigger alert
- self.page.enter_edit_state()
- self.page.click_cancel_button()
- wait = WebDriverWait(self.browser.driver, 5)
- wait.until_not(EC.alert_is_present())
- def test_cancel_without_changes_does_not_trigger_confirm_with_custom_widget_through_header(
- self,
- ):
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- self.page.click_dashboard_header_add_widget_button()
- title_input = self.browser.element(WIDGET_TITLE_FIELD)
- title_input.send_keys("New custom widget")
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- self.page.wait_until_loaded()
- # Should not trigger confirm dialog
- self.page.enter_edit_state()
- self.page.click_cancel_button()
- wait = WebDriverWait(self.browser.driver, 5)
- wait.until_not(EC.alert_is_present())
- def test_position_when_adding_multiple_widgets_through_add_widget_tile_in_edit(
- self,
- ):
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- self.page.enter_edit_state()
- # Widgets should take up the whole first row and the first spot in second row
- self.page.add_widget_through_dashboard("A")
- self.page.add_widget_through_dashboard("B")
- self.page.add_widget_through_dashboard("C")
- self.page.add_widget_through_dashboard("D")
- self.page.wait_until_loaded()
- self.page.save_dashboard()
- self.capture_screenshots(
- "dashboards - position when adding multiple widgets through Add Widget tile in edit"
- )
- def test_position_when_adding_multiple_widgets_through_add_widget_tile_in_create(
- self,
- ):
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_create_dashboard()
- # Widgets should take up the whole first row and the first spot in second row
- self.page.add_widget_through_dashboard("A")
- self.page.add_widget_through_dashboard("B")
- self.page.add_widget_through_dashboard("C")
- self.page.add_widget_through_dashboard("D")
- self.page.wait_until_loaded()
- self.page.save_dashboard()
- # Wait for page redirect, or else loading check passes too early
- wait = WebDriverWait(self.browser.driver, 10)
- wait.until(
- lambda driver: (
- f"/organizations/{self.organization.slug}/dashboards/new/"
- not in driver.current_url
- )
- )
- self.capture_screenshots(
- "dashboards - position when adding multiple widgets through Add Widget tile in create"
- )
- def test_deleting_stacked_widgets_by_context_menu_does_not_trigger_confirm_on_edit_cancel(
- self,
- ):
- layouts = [
- {"x": 0, "y": 0, "w": 2, "h": 2, "minH": 2},
- {"x": 0, "y": 2, "w": 2, "h": 2, "minH": 2},
- ]
- existing_widgets = DashboardWidget.objects.bulk_create(
- [
- DashboardWidget(
- dashboard=self.dashboard,
- order=i,
- title=f"Existing Widget {i}",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- detail={"layout": layout},
- )
- for i, layout in enumerate(layouts)
- ]
- )
- DashboardWidgetQuery.objects.bulk_create(
- DashboardWidgetQuery(
- widget=widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- for widget in existing_widgets
- )
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
- dropdown_trigger.click()
- delete_widget_menu_item = self.browser.element('[data-test-id="delete-widget"]')
- delete_widget_menu_item.click()
- confirm_button = self.browser.element('[data-test-id="confirm-button"]')
- confirm_button.click()
- wait = WebDriverWait(self.browser.driver, 5)
- wait.until(
- EC.presence_of_element_located(
- (By.XPATH, "//*[contains(text(),'Dashboard updated')]")
- )
- )
- # Should not trigger confirm dialog
- self.page.enter_edit_state()
- self.page.click_cancel_button()
- wait.until_not(EC.alert_is_present())
- def test_changing_number_widget_to_area_updates_widget_height(
- self,
- ):
- layouts = [
- (DashboardWidgetDisplayTypes.BIG_NUMBER, {"x": 0, "y": 0, "w": 2, "h": 1, "minH": 1}),
- (DashboardWidgetDisplayTypes.LINE_CHART, {"x": 0, "y": 1, "w": 2, "h": 2, "minH": 2}),
- ]
- existing_widgets = DashboardWidget.objects.bulk_create(
- [
- DashboardWidget(
- dashboard=self.dashboard,
- order=i,
- title=f"Widget {i}",
- display_type=display_type,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- detail={"layout": layout},
- )
- for i, (display_type, layout) in enumerate(layouts)
- ]
- )
- DashboardWidgetQuery.objects.bulk_create(
- DashboardWidgetQuery(
- widget=widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- for widget in existing_widgets
- )
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- # Open edit modal for first widget
- dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
- dropdown_trigger.click()
- edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
- edit_widget_menu_item.click()
- # Change the chart type to the first visualization option - Area chart
- chart_type_input = self.browser.element("#react-select-2-input")
- chart_type_input.send_keys("Area", Keys.ENTER)
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- # No confirm dialog because of shifting lower element
- self.page.enter_edit_state()
- self.page.click_cancel_button()
- wait = WebDriverWait(self.browser.driver, 5)
- wait.until_not(EC.alert_is_present())
- # Try to decrease height to 1 row, should stay at 2 rows
- self.page.enter_edit_state()
- resizeHandle = self.browser.element(WIDGET_RESIZE_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 0, -100).perform()
- self.page.save_dashboard()
- self.browser.snapshot(
- "dashboards - change from big number to area chart increases widget to min height"
- )
- def test_changing_number_widget_larger_than_min_height_for_area_chart_keeps_height(
- self,
- ):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Originally Big Number - 3 rows",
- display_type=DashboardWidgetDisplayTypes.BIG_NUMBER,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- detail={"layout": {"x": 0, "y": 0, "w": 2, "h": 3, "minH": 1}},
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- # Open edit modal for first widget
- dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
- dropdown_trigger.click()
- edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
- edit_widget_menu_item.click()
- # Change the chart type to the first visualization option - Area chart
- chart_type_input = self.browser.element("#react-select-2-input")
- chart_type_input.send_keys("Area", Keys.ENTER)
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- self.page.wait_until_loaded()
- self.browser.snapshot(
- "dashboards - change from big number to other chart of more than 2 rows keeps height"
- )
- # Try to decrease height by >1 row, should be at 2 rows
- self.page.enter_edit_state()
- resizeHandle = self.browser.element(WIDGET_RESIZE_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 0, -400).perform()
- self.page.save_dashboard()
- self.browser.snapshot(
- "dashboards - change from big number to other chart enforces min height of 2"
- )
- def test_changing_area_widget_larger_than_min_height_for_number_chart_keeps_height(
- self,
- ):
- existing_widget = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Originally Area Chart - 3 rows",
- display_type=DashboardWidgetDisplayTypes.AREA_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- detail={"layout": {"x": 0, "y": 0, "w": 2, "h": 3, "minH": 2}},
- )
- DashboardWidgetQuery.objects.create(
- widget=existing_widget, fields=["count()"], columns=[], aggregates=["count()"], order=0
- )
- with self.feature(
- FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE + WIDGET_LIBRARY_FEATURE
- ):
- self.page.visit_dashboard_detail()
- # Open edit modal for first widget
- dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
- dropdown_trigger.click()
- edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
- edit_widget_menu_item.click()
- # Change the chart type to big number
- chart_type_input = self.browser.element("#react-select-2-input")
- chart_type_input.send_keys("Big Number", Keys.ENTER)
- button = self.browser.element('[data-test-id="add-widget"]')
- button.click()
- self.page.wait_until_loaded()
- self.browser.snapshot("dashboards - change from area chart to big number keeps height")
- # Decrease height by >1 row, should stop at 1 row
- self.page.enter_edit_state()
- resizeHandle = self.browser.element(WIDGET_RESIZE_HANDLE)
- action = ActionChains(self.browser.driver)
- action.drag_and_drop_by_offset(resizeHandle, 0, -400).perform()
- self.page.save_dashboard()
- self.browser.snapshot(
- "dashboards - change from area chart to big number allows min height of 1"
- )
- class OrganizationDashboardsManageAcceptanceTest(AcceptanceTestCase):
- def setUp(self):
- super().setUp()
- self.team = self.create_team(organization=self.organization, name="Mariachi Band")
- self.project = self.create_project(
- organization=self.organization, teams=[self.team], name="Bengal"
- )
- self.dashboard = Dashboard.objects.create(
- title="Dashboard 1", created_by=self.user, organization=self.organization
- )
- self.widget_1 = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=0,
- title="Widget 1",
- display_type=DashboardWidgetDisplayTypes.LINE_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- self.widget_2 = DashboardWidget.objects.create(
- dashboard=self.dashboard,
- order=1,
- title="Widget 2",
- display_type=DashboardWidgetDisplayTypes.TABLE,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- self.login_as(self.user)
- self.default_path = f"/organizations/{self.organization.slug}/dashboards/"
- def wait_until_loaded(self):
- self.browser.wait_until_not('[data-test-id="loading-indicator"]')
- self.browser.wait_until_not('[data-test-id="loading-placeholder"]')
- def test_dashboard_manager(self):
- with self.feature(FEATURE_NAMES + EDIT_FEATURE):
- self.browser.get(self.default_path)
- self.wait_until_loaded()
- self.browser.snapshot("dashboards - manage overview")
- def test_dashboard_manager_with_unset_layouts_and_defined_layouts(self):
- dashboard_with_layouts = Dashboard.objects.create(
- title="Dashboard with some defined layouts",
- created_by=self.user,
- organization=self.organization,
- )
- DashboardWidget.objects.create(
- dashboard=dashboard_with_layouts,
- order=0,
- title="Widget 1",
- display_type=DashboardWidgetDisplayTypes.BAR_CHART,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- detail={"layout": {"x": 1, "y": 0, "w": 3, "h": 3, "minH": 2}},
- )
- # This widget has no layout, but should position itself at
- # x: 4, y: 0, w: 2, h: 2
- DashboardWidget.objects.create(
- dashboard=dashboard_with_layouts,
- order=1,
- title="Widget 2",
- display_type=DashboardWidgetDisplayTypes.TABLE,
- widget_type=DashboardWidgetTypes.DISCOVER,
- interval="1d",
- )
- with self.feature(FEATURE_NAMES + EDIT_FEATURE + GRID_LAYOUT_FEATURE):
- self.browser.get(self.default_path)
- self.wait_until_loaded()
- self.browser.snapshot("dashboards - manage overview with grid layout")
|