diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 79257c3d6da..6994d45fb40 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -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; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 567c6d566e7..01b37ad9386 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -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", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 0118448325c..88af45a4d15 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -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 diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index fbc93fc3fc9..f26a754d574 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -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" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index d60e4938bf0..caf8239adce 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -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", diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/CanvasGridAddActions.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/CanvasGridAddActions.tsx index 47c5adb2453..d5d43c90ca1 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/CanvasGridAddActions.tsx +++ b/public/app/features/dashboard-scene/scene/layouts-shared/CanvasGridAddActions.tsx @@ -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 (