diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 6b14a65cfda..fa1800d42fe 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -161,6 +161,7 @@ Experimental features might be changed or removed without prior notice. | `annotationPermissionUpdate` | Separate annotation permissions from dashboard permissions to allow for more granular control. | | `extractFieldsNameDeduplication` | Make sure extracted field names are unique in the dataframe | | `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | +| `dashboardScene` | Enables dashboard rendering using scenes for all roles | | `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | | `flameGraphItemCollapsing` | Allow collapsing of flame graph items | diff --git a/package.json b/package.json index 472baf98257..f1884add019 100644 --- a/package.json +++ b/package.json @@ -254,7 +254,7 @@ "@grafana/lezer-traceql": "0.0.10", "@grafana/monaco-logql": "^0.0.7", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^1.20.1", + "@grafana/scenes": "^1.21.1", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "^7.4.0", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 522e4a9e684..fa480288564 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -155,6 +155,7 @@ export interface FeatureToggles { annotationPermissionUpdate?: boolean; extractFieldsNameDeduplication?: boolean; dashboardSceneForViewers?: boolean; + dashboardScene?: boolean; panelFilterVariable?: boolean; pdfTables?: boolean; ssoSettingsApi?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 1b5751c6707..6824726731b 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -993,6 +993,13 @@ var ( FrontendOnly: true, Owner: grafanaDashboardsSquad, }, + { + Name: "dashboardScene", + Description: "Enables dashboard rendering using scenes for all roles", + 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 22ce47dc46c..e444b8606e6 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -136,6 +136,7 @@ alertmanagerRemoteOnly,experimental,@grafana/alerting-squad,false,false,false,fa annotationPermissionUpdate,experimental,@grafana/identity-access-team,false,false,false,false extractFieldsNameDeduplication,experimental,@grafana/grafana-bi-squad,false,false,false,true dashboardSceneForViewers,experimental,@grafana/dashboards-squad,false,false,false,true +dashboardScene,experimental,@grafana/dashboards-squad,false,false,false,true panelFilterVariable,experimental,@grafana/dashboards-squad,false,false,false,true pdfTables,privatePreview,@grafana/sharing-squad,false,false,false,false ssoSettingsApi,experimental,@grafana/identity-access-team,true,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 312f232abad..f39cb232761 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -555,6 +555,10 @@ const ( // Enables dashboard rendering using Scenes for viewer roles FlagDashboardSceneForViewers = "dashboardSceneForViewers" + // FlagDashboardScene + // Enables dashboard rendering using scenes for all roles + FlagDashboardScene = "dashboardScene" + // FlagPanelFilterVariable // Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard FlagPanelFilterVariable = "panelFilterVariable" diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts index 5689ee6c4e8..5b988c550a8 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts @@ -120,6 +120,11 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte //return onUpdatePanelSnapshotData(this.props.panel, frames); return Promise.resolve(true); }; + + // Backward compatibility with id + context.instanceState = { + legacyPanelId: getPanelIdForVizPanel(vizPanel), + }; } function getBuiltInAnnotationsLayer(scene: DashboardScene): dataLayers.AnnotationsDataLayer | undefined { diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts index fa705c8ab74..dcb50474ad5 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts @@ -47,6 +47,14 @@ describe('DashboardModelCompatibilityWrapper', () => { expect(wrapper.getPanelById(1)!.title).toBe('Panel A'); expect(wrapper.getPanelById(2)!.title).toBe('Panel B'); }); + + it('Can remove panel', () => { + const { wrapper, scene } = setup(); + + wrapper.removePanel(wrapper.getPanelById(1)!); + + expect((scene.state.body as SceneGridLayout).state.children.length).toBe(1); + }); }); function setup() { diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts index 6cce5418c78..b4daca0449b 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts @@ -2,11 +2,19 @@ import { Subscription } from 'rxjs'; import { AnnotationQuery, DashboardCursorSync, dateTimeFormat, DateTimeInput, EventBusSrv } from '@grafana/data'; import { TimeRangeUpdatedEvent } from '@grafana/runtime'; -import { behaviors, SceneDataTransformer, sceneGraph, VizPanel } from '@grafana/scenes'; +import { + behaviors, + SceneDataTransformer, + sceneGraph, + SceneGridItem, + SceneGridLayout, + SceneGridRow, + VizPanel, +} from '@grafana/scenes'; import { DashboardScene } from '../scene/DashboardScene'; -import { findVizPanelByKey, getVizPanelKeyForPanelId } from './utils'; +import { findVizPanelByKey, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from './utils'; /** * Will move this to make it the main way we remain somewhat compatible with getDashboardSrv().getCurrent @@ -104,9 +112,49 @@ export class DashboardModelCompatibilityWrapper { return null; } + /** + * Mainly implemented to support Getting started panel's dissmis button. + */ public removePanel(panel: PanelCompatibilityWrapper) { - // TODO - console.error('Scenes DashboardModelCompatibilityWrapper.removePanel not implemented (yet)'); + const vizPanel = findVizPanelByKey(this._scene, getVizPanelKeyForPanelId(panel.id)); + if (!vizPanel) { + console.error('Trying to remove a panel that was not found in scene', panel); + return; + } + + const gridItem = vizPanel.parent; + if (!(gridItem instanceof SceneGridItem)) { + console.error('Trying to remove a panel that is not wrapped in SceneGridItem'); + return; + } + + const layout = sceneGraph.getLayout(vizPanel); + if (!(layout instanceof SceneGridLayout)) { + console.error('Trying to remove a panel in a layout that is not SceneGridLayout '); + return; + } + + // if grid item is directly in the layout just remove it + if (layout === gridItem.parent) { + layout.setState({ + children: layout.state.children.filter((child) => child !== gridItem), + }); + } + + // Removing from a row is a bit more complicated + if (gridItem.parent instanceof SceneGridRow) { + // Clone the row and remove the grid item + const newRow = layout.clone({ + children: layout.state.children.filter((child) => child !== gridItem), + }); + + // Now update the grid layout and replace the row with the updated one + if (layout.parent instanceof SceneGridLayout) { + layout.parent.setState({ + children: layout.parent.state.children.map((child) => (child === layout ? newRow : child)), + }); + } + } } public canEditAnnotations(dashboardUID?: string) { @@ -125,6 +173,17 @@ export class DashboardModelCompatibilityWrapper { class PanelCompatibilityWrapper { constructor(private _vizPanel: VizPanel) {} + public get id() { + const id = getPanelIdForVizPanel(this._vizPanel); + + if (isNaN(id)) { + console.error('VizPanel key could not be translated to a legacy numeric panel id', this._vizPanel); + return 0; + } + + return id; + } + public get type() { return this._vizPanel.state.pluginId; } diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index 019eedfe11b..5d895b96c90 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -18,6 +18,10 @@ export type DashboardPageProxyProps = GrafanaRouteComponentProps< // This proxy component is used for Dashboard -> Scenes migration. // It will render DashboardScenePage if the user is only allowed to view the dashboard. function DashboardPageProxy(props: DashboardPageProxyProps) { + if (config.featureToggles.dashboardScene) { + return ; + } + const stateManager = getDashboardScenePageStateManager(); const isScenesSupportedRoute = Boolean( props.route.routeName === DashboardRoutes.Home || @@ -26,6 +30,7 @@ function DashboardPageProxy(props: DashboardPageProxyProps) { // We pre-fetch dashboard to render dashboard page component depending on dashboard permissions. // To avoid querying single dashboard multiple times, stateManager.fetchDashboard uses a simple, short-lived cache. + // eslint-disable-next-line react-hooks/rules-of-hooks const dashboard = useAsync(async () => { const dashToFetch = props.route.routeName === DashboardRoutes.Home ? props.route.routeName : props.match.params.uid; diff --git a/yarn.lock b/yarn.lock index 37041a65a2b..cfb2d13997a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3320,9 +3320,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^1.20.1": - version: 1.20.1 - resolution: "@grafana/scenes@npm:1.20.1" +"@grafana/scenes@npm:^1.21.1": + version: 1.21.1 + resolution: "@grafana/scenes@npm:1.21.1" dependencies: "@grafana/e2e-selectors": "npm:10.0.2" react-grid-layout: "npm:1.3.4" @@ -3334,7 +3334,7 @@ __metadata: "@grafana/runtime": 10.0.3 "@grafana/schema": 10.0.3 "@grafana/ui": 10.0.3 - checksum: 0edf9985ae61d5ddd0b6c015684fe06aa7c0ab3c085e0eeb0377bb5b65dccc6e91f3e53e0886d832a3e31b9b20b368027a6b1bf508e8d8ac1779cfd4789e7d20 + checksum: f9621b0edcc5a9da2cfeac679bf9ea8d2ae6fc64c635f5bf8ee90c47cdf7a8e8799b4ef4d41b1fdee056371e9f5bfc73f5e5b2dc23852a1b05963748559fd6e9 languageName: node linkType: hard @@ -17325,7 +17325,7 @@ __metadata: "@grafana/lezer-traceql": "npm:0.0.10" "@grafana/monaco-logql": "npm:^0.0.7" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^1.20.1" + "@grafana/scenes": "npm:^1.21.1" "@grafana/schema": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" "@grafana/ui": "workspace:*"