Dashboard: Prevent changing layout to tabs when rows contain tabs (#116019)
- Add containsTabsLayout helper function to check if child layouts contain tabs - Update DashboardLayoutSelector to disable tabs option when children contain tabs - Show different tooltip message for parent vs child tabs nesting scenarios - Add tests for the new functionality
This commit is contained in:
+64
@@ -7,10 +7,13 @@ import { SceneGridLayout, VizPanel, SceneVariableSet } from '@grafana/scenes';
|
||||
|
||||
import { activateFullSceneTree } from '../../utils/test-utils';
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager';
|
||||
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 { TabItem } from '../layout-tabs/TabItem';
|
||||
import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
|
||||
import { LayoutParent } from '../types/LayoutParent';
|
||||
|
||||
import { DashboardLayoutSelector } from './DashboardLayoutSelector';
|
||||
@@ -40,6 +43,27 @@ describe('DashboardLayoutSelector', () => {
|
||||
await user.click(confirmButton);
|
||||
expect(switchLayoutMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should disable tabs option when a row contains tabs layout and show correct message', async () => {
|
||||
const scene = buildTestSceneWithNestedTabs();
|
||||
const layoutManager = scene.state.body;
|
||||
|
||||
render(<DashboardLayoutSelector layoutManager={layoutManager} />);
|
||||
|
||||
const tabsOption = screen.getByLabelText('layout-selection-option-Tabs');
|
||||
expect(tabsOption).toBeDisabled();
|
||||
expect(screen.getByTitle('Cannot change to tabs because a row already contains tabs')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not disable tabs option when rows do not contain tabs', async () => {
|
||||
const scene = buildTestScene();
|
||||
const layoutManager = scene.state.body;
|
||||
|
||||
render(<DashboardLayoutSelector layoutManager={layoutManager} />);
|
||||
|
||||
const tabsOption = screen.getByLabelText('layout-selection-option-Tabs');
|
||||
expect(tabsOption).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
const buildTestScene = () => {
|
||||
@@ -70,3 +94,43 @@ const buildTestScene = () => {
|
||||
activateFullSceneTree(scene);
|
||||
return scene;
|
||||
};
|
||||
|
||||
const buildTestSceneWithNestedTabs = () => {
|
||||
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' }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
new RowItem({
|
||||
title: 'Row with Tabs',
|
||||
layout: new TabsLayoutManager({
|
||||
tabs: [
|
||||
new TabItem({
|
||||
title: 'Tab 1',
|
||||
layout: AutoGridLayoutManager.createEmpty(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
activateFullSceneTree(scene);
|
||||
return scene;
|
||||
};
|
||||
|
||||
+21
-6
@@ -11,6 +11,7 @@ import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
|
||||
import { isLayoutParent } from '../types/LayoutParent';
|
||||
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
|
||||
|
||||
import { containsTabsLayout } from './findAllGridTypes';
|
||||
import { layoutRegistry } from './layoutRegistry';
|
||||
|
||||
export interface Props {
|
||||
@@ -22,19 +23,26 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||
const options = layoutRegistry.list().filter((layout) => layout.isGridLayout === isGridLayout);
|
||||
const [newLayout, setNewLayout] = useState<LayoutRegistryItem | undefined>();
|
||||
|
||||
const disableTabs = useMemo(() => {
|
||||
const disableTabsReason = useMemo(() => {
|
||||
if (config.featureToggles.unlimitedLayoutsNesting) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Check parent hierarchy
|
||||
let parent = layoutManager.parent;
|
||||
while (parent) {
|
||||
if (parent instanceof TabsLayoutManager) {
|
||||
return true;
|
||||
return 'parent';
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Check child hierarchy
|
||||
if (containsTabsLayout(layoutManager)) {
|
||||
return 'child';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [layoutManager]);
|
||||
|
||||
const onChangeLayout = useCallback((newLayout: LayoutRegistryItem) => setNewLayout(newLayout), []);
|
||||
@@ -59,8 +67,15 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
|
||||
|
||||
const radioOptions = options.map((opt) => {
|
||||
let description = opt.description;
|
||||
if (disableTabs && opt.id === TabsLayoutManager.descriptor.id) {
|
||||
description = t('dashboard.canvas-actions.disabled-nested-tabs', 'Tabs cannot be nested inside other tabs');
|
||||
if (disableTabsReason && opt.id === TabsLayoutManager.descriptor.id) {
|
||||
if (disableTabsReason === 'parent') {
|
||||
description = t('dashboard.canvas-actions.disabled-nested-tabs', 'Tabs cannot be nested inside other tabs');
|
||||
} else {
|
||||
description = t(
|
||||
'dashboard.canvas-actions.disabled-child-contains-tabs',
|
||||
'Cannot change to tabs because a row already contains tabs'
|
||||
);
|
||||
}
|
||||
disabledOptions.push(opt);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager';
|
||||
import { RowItem } from '../layout-rows/RowItem';
|
||||
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
|
||||
import { TabItem } from '../layout-tabs/TabItem';
|
||||
import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
|
||||
|
||||
import { containsTabsLayout, findAllGridTypes } from './findAllGridTypes';
|
||||
|
||||
describe('findAllGridTypes', () => {
|
||||
it('should return grid type for a grid layout', () => {
|
||||
const layout = AutoGridLayoutManager.createEmpty();
|
||||
expect(findAllGridTypes(layout)).toEqual([AutoGridLayoutManager.descriptor.id]);
|
||||
});
|
||||
|
||||
it('should return grid types from tabs', () => {
|
||||
const layout = new TabsLayoutManager({
|
||||
tabs: [
|
||||
new TabItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
new TabItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
],
|
||||
});
|
||||
expect(findAllGridTypes(layout)).toEqual([
|
||||
AutoGridLayoutManager.descriptor.id,
|
||||
AutoGridLayoutManager.descriptor.id,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return grid types from rows', () => {
|
||||
const layout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
],
|
||||
});
|
||||
expect(findAllGridTypes(layout)).toEqual([
|
||||
AutoGridLayoutManager.descriptor.id,
|
||||
AutoGridLayoutManager.descriptor.id,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('containsTabsLayout', () => {
|
||||
it('should return true when layout is TabsLayoutManager', () => {
|
||||
const layout = new TabsLayoutManager({
|
||||
tabs: [new TabItem({ layout: AutoGridLayoutManager.createEmpty() })],
|
||||
});
|
||||
expect(containsTabsLayout(layout)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when layout is a grid layout', () => {
|
||||
const layout = AutoGridLayoutManager.createEmpty();
|
||||
expect(containsTabsLayout(layout)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when layout is RowsLayoutManager with no tabs in rows', () => {
|
||||
const layout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
],
|
||||
});
|
||||
expect(containsTabsLayout(layout)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when RowsLayoutManager contains a row with tabs layout', () => {
|
||||
const layout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
new RowItem({
|
||||
layout: new TabsLayoutManager({
|
||||
tabs: [new TabItem({ layout: AutoGridLayoutManager.createEmpty() })],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
expect(containsTabsLayout(layout)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when any row contains tabs layout', () => {
|
||||
const layout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({
|
||||
layout: new TabsLayoutManager({
|
||||
tabs: [new TabItem({ layout: AutoGridLayoutManager.createEmpty() })],
|
||||
}),
|
||||
}),
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
new RowItem({ layout: AutoGridLayoutManager.createEmpty() }),
|
||||
],
|
||||
});
|
||||
expect(containsTabsLayout(layout)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -15,3 +15,15 @@ export function findAllGridTypes(layout: DashboardLayoutManager): string[] {
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function containsTabsLayout(layout: DashboardLayoutManager): boolean {
|
||||
if (layout instanceof TabsLayoutManager) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (layout instanceof RowsLayoutManager) {
|
||||
return layout.state.rows.some((row) => containsTabsLayout(row.getLayout()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4614,6 +4614,7 @@
|
||||
},
|
||||
"canvas-actions": {
|
||||
"add-panel": "Add panel",
|
||||
"disabled-child-contains-tabs": "Cannot change to tabs because a row already contains tabs",
|
||||
"disabled-nested-grouping": "Grouping is limited to 2 levels",
|
||||
"disabled-nested-tabs": "Tabs cannot be nested inside other tabs",
|
||||
"group-into-row": "Group into row",
|
||||
|
||||
Reference in New Issue
Block a user