Dashboard: Limit panel grouping depth (#112216)

This commit is contained in:
Sergej-Vlasov
2025-10-10 15:15:54 +03:00
committed by GitHub
parent 43b0e04598
commit b5613b3724
8 changed files with 119 additions and 9 deletions
@@ -396,6 +396,10 @@ export interface FeatureToggles {
*/
dashboardUndoRedo?: boolean;
/**
* Enables unlimited dashboard panel grouping
*/
unlimitedLayoutsNesting?: boolean;
/**
* Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard
*/
panelFilterVariable?: boolean;
+7
View File
@@ -657,6 +657,13 @@ var (
FrontendOnly: true,
Owner: grafanaDashboardsSquad,
},
{
Name: "unlimitedLayoutsNesting",
Description: "Enables unlimited dashboard panel grouping",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaDashboardsSquad,
},
{
Name: "panelFilterVariable",
Description: "Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard",
+1
View File
@@ -87,6 +87,7 @@ dashboardSceneSolo,GA,@grafana/dashboards-squad,false,false,true
dashboardScene,GA,@grafana/dashboards-squad,false,false,true
dashboardNewLayouts,experimental,@grafana/dashboards-squad,false,false,true
dashboardUndoRedo,experimental,@grafana/dashboards-squad,false,false,true
unlimitedLayoutsNesting,experimental,@grafana/dashboards-squad,false,false,true
panelFilterVariable,experimental,@grafana/dashboards-squad,false,false,true
pdfTables,preview,@grafana/grafana-operator-experience-squad,false,false,false
canvasPanelPanZoom,preview,@grafana/dataviz-squad,false,false,true
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
87 dashboardScene GA @grafana/dashboards-squad false false true
88 dashboardNewLayouts experimental @grafana/dashboards-squad false false true
89 dashboardUndoRedo experimental @grafana/dashboards-squad false false true
90 unlimitedLayoutsNesting experimental @grafana/dashboards-squad false false true
91 panelFilterVariable experimental @grafana/dashboards-squad false false true
92 pdfTables preview @grafana/grafana-operator-experience-squad false false false
93 canvasPanelPanZoom preview @grafana/dataviz-squad false false true
+4
View File
@@ -359,6 +359,10 @@ const (
// Enables undo/redo in dynamic dashboards
FlagDashboardUndoRedo = "dashboardUndoRedo"
// FlagUnlimitedLayoutsNesting
// Enables unlimited dashboard panel grouping
FlagUnlimitedLayoutsNesting = "unlimitedLayoutsNesting"
// FlagPanelFilterVariable
// Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard
FlagPanelFilterVariable = "panelFilterVariable"
+13
View File
@@ -3897,6 +3897,19 @@
"hideFromDocs": true
}
},
{
"metadata": {
"name": "unlimitedLayoutsNesting",
"resourceVersion": "1760013838902",
"creationTimestamp": "2025-10-09T12:43:58Z"
},
"spec": {
"description": "Enables unlimited dashboard panel grouping",
"stage": "experimental",
"codeowner": "@grafana/dashboards-squad",
"frontend": true
}
},
{
"metadata": {
"name": "useKubernetesShortURLsAPI",
@@ -1,8 +1,10 @@
import { css, cx } from '@emotion/css';
import { useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Button, Dropdown, Menu, useStyles2 } from '@grafana/ui';
import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph';
@@ -11,7 +13,7 @@ import { getDefaultVizPanel } from '../../utils/utils';
import { DashboardScene } from '../DashboardScene';
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { DashboardLayoutManager, isDashboardLayoutManager } from '../types/DashboardLayoutManager';
import { addNewRowTo, addNewTabTo } from './addNew';
import { useClipboardState } from './useClipboardState';
@@ -25,6 +27,38 @@ export function CanvasGridAddActions({ layoutManager }: Props) {
const styles = useStyles2(getStyles);
const { hasCopiedPanel } = useClipboardState();
const { disableGrouping, disableTabs } = useMemo(() => {
if (config.featureToggles.unlimitedLayoutsNesting) {
return { disableGrouping: false, disableTabs: false };
}
let parent = layoutManager.parent;
const layouts = [];
while (parent) {
if (isDashboardLayoutManager(parent)) {
layouts.push(parent.descriptor.id);
}
if (layouts.length === 2) {
parent = undefined;
break;
}
parent = parent.parent;
}
if (layouts.length === 2) {
return { disableGrouping: true, disableTabs: true };
}
if (layouts.length === 1 && layouts[0] === TabsLayoutManager.descriptor.id) {
return { disableGrouping: false, disableTabs: true };
}
return { disableGrouping: false, disableTabs: false };
}, [layoutManager]);
return (
<div className={cx(styles.addAction, 'dashboard-canvas-add-button')}>
<Button
@@ -55,6 +89,12 @@ export function CanvasGridAddActions({ layoutManager }: Props) {
icon="layers"
testId={selectors.components.CanvasGridAddActions.addTab}
label={t('dashboard.canvas-actions.group-into-tab', 'Group into tab')}
disabled={disableTabs}
description={
disableTabs
? t('dashboard.canvas-actions.disabled-nested-tabs', 'Tabs cannot be nested inside other tabs')
: undefined
}
onClick={() => {
addNewTabTo(layoutManager);
DashboardInteractions.trackGroupTabClick();
@@ -68,6 +108,12 @@ export function CanvasGridAddActions({ layoutManager }: Props) {
fill="text"
icon="layers"
data-testid={selectors.components.CanvasGridAddActions.groupPanels}
disabled={disableGrouping}
tooltip={
disableGrouping
? t('dashboard.canvas-actions.disabled-nested-grouping', 'Grouping is limited to 2 levels')
: undefined
}
>
<Trans i18nKey="dashboard.canvas-actions.group-panels">Group panels</Trans>
</Button>
@@ -1,10 +1,12 @@
import { useCallback, useMemo } from 'react';
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { RadioButtonGroup, Box } from '@grafana/ui';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { isLayoutParent } from '../types/LayoutParent';
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
@@ -19,6 +21,21 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
const isGridLayout = layoutManager.descriptor.isGridLayout;
const options = layoutRegistry.list().filter((layout) => layout.isGridLayout === isGridLayout);
const disableTabs = useMemo(() => {
if (config.featureToggles.unlimitedLayoutsNesting) {
return false;
}
let parent = layoutManager.parent;
while (parent) {
if (parent instanceof TabsLayoutManager) {
return true;
}
parent = parent.parent;
}
return false;
}, [layoutManager]);
const onChangeLayout = useCallback(
(newLayout: LayoutRegistryItem) => {
const layoutParent = layoutManager.parent;
@@ -30,17 +47,33 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
[layoutManager]
);
const radioOptions = options.map((opt) => ({
value: opt,
label: opt.name,
icon: opt.icon,
description: opt.description,
ariaLabel: `layout-selection-option-${opt.name}`,
}));
const disabledOptions: LayoutRegistryItem[] = [];
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');
disabledOptions.push(opt);
}
return {
value: opt,
label: opt.name,
icon: opt.icon,
description,
ariaLabel: `layout-selection-option-${opt.name}`,
};
});
return (
<Box paddingBottom={2} display="flex" grow={1} alignItems="center">
<RadioButtonGroup fullWidth value={layoutManager.descriptor} options={radioOptions} onChange={onChangeLayout} />
<RadioButtonGroup
fullWidth
value={layoutManager.descriptor}
options={radioOptions}
onChange={onChangeLayout}
disabledOptions={disabledOptions}
/>
</Box>
);
}
+2
View File
@@ -4496,6 +4496,8 @@
},
"canvas-actions": {
"add-panel": "Add panel",
"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",
"group-into-tab": "Group into tab",
"group-panels": "Group panels",