Dashboard: Implement modal to confirm layout change (#111093)
This commit is contained in:
@@ -419,6 +419,9 @@ test.describe(
|
||||
// Select tabs layout
|
||||
await page.getByLabel('layout-selection-option-Tabs').click();
|
||||
|
||||
// confirm layout change
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete).click();
|
||||
|
||||
await expect(dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('New row'))).toBeVisible();
|
||||
await expect(dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('New row 1'))).toBeVisible();
|
||||
await expect(
|
||||
@@ -757,6 +760,9 @@ test.describe(
|
||||
// Select rows layout
|
||||
await page.getByLabel('layout-selection-option-Rows').click();
|
||||
|
||||
// confirm layout change
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete).click();
|
||||
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.DashboardRow.wrapper('New tab 1'))
|
||||
.scrollIntoViewIfNeeded();
|
||||
|
||||
@@ -4,6 +4,8 @@ import { test, expect, E2ESelectorGroups, DashboardPage } from '@grafana/plugin-
|
||||
|
||||
import testV2Dashboard from '../dashboards/TestV2Dashboard.json';
|
||||
|
||||
import { switchToAutoGrid } from './utils';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: true,
|
||||
@@ -33,7 +35,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('New panel'))
|
||||
@@ -64,7 +67,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
// Get initial positions - standard width should have panels on different rows
|
||||
const firstPanelTop = await getPanelTop(dashboardPage, selectors);
|
||||
@@ -124,7 +128,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth)
|
||||
@@ -181,7 +186,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.PanelEditor.ElementEditPane.AutoGridLayout.maxColumns)
|
||||
@@ -216,7 +222,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
const regularRowHeight = await getPanelHeight(dashboardPage, selectors);
|
||||
|
||||
@@ -271,7 +278,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
const regularRowHeight = await getPanelHeight(dashboardPage, selectors);
|
||||
|
||||
@@ -328,7 +336,8 @@ test.describe(
|
||||
).toHaveCount(3);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
// Set narrow column width first to ensure panels fit horizontally
|
||||
await dashboardPage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Page } from 'playwright-core';
|
||||
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
import { test, expect, DashboardPage } from '@grafana/plugin-e2e';
|
||||
|
||||
import testV2DashWithRepeats from '../dashboards/V2DashWithRepeats.json';
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getPanelPosition,
|
||||
importTestDashboard,
|
||||
goToEmbeddedPanel,
|
||||
switchToAutoGrid,
|
||||
} from './utils';
|
||||
|
||||
const repeatTitleBase = 'repeat - ';
|
||||
@@ -42,7 +43,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('New panel')).first().click();
|
||||
|
||||
@@ -78,7 +79,8 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
await saveDashboard(dashboardPage, page, selectors);
|
||||
await page.reload();
|
||||
|
||||
@@ -117,7 +119,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
// select first/original repeat panel to activate edit pane
|
||||
await dashboardPage
|
||||
@@ -148,7 +150,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
await saveDashboard(dashboardPage, page, selectors);
|
||||
await page.reload();
|
||||
|
||||
@@ -214,7 +216,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
await saveDashboard(dashboardPage, page, selectors);
|
||||
|
||||
// loading directly into panel editor
|
||||
@@ -271,7 +273,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
|
||||
// this moving repeated panel between two normal panels
|
||||
await movePanel(dashboardPage, selectors, `${repeatTitleBase}${repeatOptions.at(0)}`, 'New panel');
|
||||
@@ -319,7 +321,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
await saveDashboard(dashboardPage, page, selectors);
|
||||
await page.reload();
|
||||
|
||||
@@ -382,7 +384,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
await saveDashboard(dashboardPage, page, selectors);
|
||||
await page.reload();
|
||||
|
||||
@@ -410,7 +412,7 @@ test.describe(
|
||||
await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click();
|
||||
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click();
|
||||
|
||||
await switchToAutoGrid(page);
|
||||
await switchToAutoGrid(page, dashboardPage);
|
||||
await saveDashboard(dashboardPage, page, selectors);
|
||||
await page.reload();
|
||||
|
||||
@@ -462,7 +464,3 @@ test.describe(
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
async function switchToAutoGrid(page: Page) {
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { DashboardPage, E2ESelectorGroups, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import testV2Dashboard from '../dashboards/TestV2Dashboard.json';
|
||||
@@ -239,3 +240,12 @@ export async function getTabPosition(dashboardPage: DashboardPage, selectors: E2
|
||||
const boundingBox = await tab.boundingBox();
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
export async function switchToAutoGrid(page: Page, dashboardPage: DashboardPage) {
|
||||
await page.getByLabel('layout-selection-option-Auto grid').click();
|
||||
// confirm layout change if applicable
|
||||
const confirmModal = dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete);
|
||||
if (confirmModal) {
|
||||
await confirmModal.click();
|
||||
}
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { getPanelPlugin } from '@grafana/data/test';
|
||||
import { setPluginImportUtils } from '@grafana/runtime';
|
||||
import { SceneGridLayout, VizPanel, SceneVariableSet } from '@grafana/scenes';
|
||||
|
||||
import { activateFullSceneTree } from '../../utils/test-utils';
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
import { DashboardGridItem } from '../layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
|
||||
import { RowItem } from '../layout-rows/RowItem';
|
||||
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
|
||||
import { LayoutParent } from '../types/LayoutParent';
|
||||
|
||||
import { DashboardLayoutSelector } from './DashboardLayoutSelector';
|
||||
|
||||
const switchLayoutMock = jest.fn();
|
||||
|
||||
setPluginImportUtils({
|
||||
importPanelPlugin: (_) => Promise.resolve(getPanelPlugin({})),
|
||||
getPanelPluginFromCache: (_) => undefined,
|
||||
});
|
||||
|
||||
describe('DashboardLayoutSelector', () => {
|
||||
it('should show confirmation modal when switching layouts', async () => {
|
||||
const user = userEvent.setup();
|
||||
const scene = buildTestScene();
|
||||
const layoutManager = scene.state.body;
|
||||
(layoutManager.parent as LayoutParent).switchLayout = switchLayoutMock;
|
||||
|
||||
render(<DashboardLayoutSelector layoutManager={layoutManager} />);
|
||||
|
||||
await user.click(screen.getByLabelText('layout-selection-option-Tabs'));
|
||||
|
||||
const confirmButton = screen.getByRole('button', { name: 'Change layout' });
|
||||
|
||||
expect(confirmButton).toBeInTheDocument();
|
||||
|
||||
await user.click(confirmButton);
|
||||
expect(switchLayoutMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const buildTestScene = () => {
|
||||
const scene = new DashboardScene({
|
||||
title: 'testScene',
|
||||
editable: true,
|
||||
$variables: new SceneVariableSet({
|
||||
variables: [],
|
||||
}),
|
||||
body: new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({
|
||||
title: 'Row 1',
|
||||
layout: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
body: new VizPanel({ key: 'panel-1', pluginId: 'text' }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
activateFullSceneTree(scene);
|
||||
return scene;
|
||||
};
|
||||
+39
-19
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RadioButtonGroup, Box } from '@grafana/ui';
|
||||
import { RadioButtonGroup, Box, ConfirmModal } from '@grafana/ui';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface Props {
|
||||
export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||
const isGridLayout = layoutManager.descriptor.isGridLayout;
|
||||
const options = layoutRegistry.list().filter((layout) => layout.isGridLayout === isGridLayout);
|
||||
const [newLayout, setNewLayout] = useState<LayoutRegistryItem | undefined>();
|
||||
|
||||
const disableTabs = useMemo(() => {
|
||||
if (config.featureToggles.unlimitedLayoutsNesting) {
|
||||
@@ -36,16 +37,23 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||
return false;
|
||||
}, [layoutManager]);
|
||||
|
||||
const onChangeLayout = useCallback(
|
||||
(newLayout: LayoutRegistryItem) => {
|
||||
const layoutParent = layoutManager.parent;
|
||||
const onChangeLayout = useCallback((newLayout: LayoutRegistryItem) => setNewLayout(newLayout), []);
|
||||
|
||||
if (layoutParent && isLayoutParent(layoutParent)) {
|
||||
layoutParent.switchLayout(newLayout.createFromLayout(layoutManager));
|
||||
}
|
||||
},
|
||||
[layoutManager]
|
||||
);
|
||||
const onConfirmNewLayout = useCallback(() => {
|
||||
if (!newLayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
const layoutParent = layoutManager.parent;
|
||||
|
||||
if (layoutParent && isLayoutParent(layoutParent)) {
|
||||
layoutParent.switchLayout(newLayout.createFromLayout(layoutManager));
|
||||
}
|
||||
|
||||
setNewLayout(undefined);
|
||||
}, [newLayout, layoutManager]);
|
||||
|
||||
const onDismissNewLayout = useCallback(() => setNewLayout(undefined), []);
|
||||
|
||||
const disabledOptions: LayoutRegistryItem[] = [];
|
||||
|
||||
@@ -66,15 +74,27 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||
});
|
||||
|
||||
return (
|
||||
<Box paddingBottom={2} display="flex" grow={1} alignItems="stretch" gap={2} direction={'column'}>
|
||||
<RadioButtonGroup
|
||||
fullWidth
|
||||
value={layoutManager.descriptor}
|
||||
options={radioOptions}
|
||||
onChange={onChangeLayout}
|
||||
disabledOptions={disabledOptions}
|
||||
<>
|
||||
<Box paddingBottom={2} display="flex" grow={1} alignItems="stretch" gap={2} direction={'column'}>
|
||||
<RadioButtonGroup
|
||||
fullWidth
|
||||
value={layoutManager.descriptor}
|
||||
options={radioOptions}
|
||||
onChange={onChangeLayout}
|
||||
disabledOptions={disabledOptions}
|
||||
/>
|
||||
</Box>
|
||||
<ConfirmModal
|
||||
isOpen={!!newLayout}
|
||||
title={t('dashboard.layout.panel.modal.title', 'Change layout')}
|
||||
body={t('dashboard.layout.panel.modal.body', 'Changing the layout will reset all panel positions and sizes.')}
|
||||
confirmText={t('dashboard.layout.panel.modal.confirm', 'Change layout')}
|
||||
dismissText={t('dashboard.layout.panel.modal.dismiss', 'Cancel')}
|
||||
confirmButtonVariant="primary"
|
||||
onConfirm={onConfirmNewLayout}
|
||||
onDismiss={onDismissNewLayout}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export function useLayoutCategory(layoutManager: DashboardLayoutManager) {
|
||||
|
||||
@@ -5093,6 +5093,14 @@
|
||||
"layout": "Layout"
|
||||
},
|
||||
"continue": "Continue",
|
||||
"panel": {
|
||||
"modal": {
|
||||
"body": "Changing the layout will reset all panel positions and sizes.",
|
||||
"confirm": "Change layout",
|
||||
"dismiss": "Cancel",
|
||||
"title": "Change layout"
|
||||
}
|
||||
},
|
||||
"ungroup-nested-text": "This will ungroup all nested groups.",
|
||||
"ungroup-nested-title": "Ungroup nested groups?"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user