diff --git a/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts
index c35eb03bf84..ad954267c85 100644
--- a/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts
+++ b/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts
@@ -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();
diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts
index ffacaa13912..803f9d18d46 100644
--- a/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts
+++ b/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts
@@ -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
diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts
index dbba5c583c1..3e25adc6c41 100644
--- a/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts
+++ b/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts
@@ -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();
-}
diff --git a/e2e-playwright/dashboard-new-layouts/utils.ts b/e2e-playwright/dashboard-new-layouts/utils.ts
index 83508063ef5..69851994812 100644
--- a/e2e-playwright/dashboard-new-layouts/utils.ts
+++ b/e2e-playwright/dashboard-new-layouts/utils.ts
@@ -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();
+ }
+}
diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx
new file mode 100644
index 00000000000..d363a95ddf0
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx
@@ -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();
+
+ 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;
+};
diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx
index e959352986a..b32555b32f8 100644
--- a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx
+++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx
@@ -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();
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 (
-
-
+
+
+
+
-
+ >
);
}
export function useLayoutCategory(layoutManager: DashboardLayoutManager) {
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 761ed8f51ec..90b047262a3 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -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?"
},