Scopes: Replace scopes bridge with scopes variable (#105399)

This commit is contained in:
Torkel Ödegaard
2025-05-19 16:31:37 +02:00
committed by GitHub
parent cfe340da05
commit 8ee1f2c1fc
14 changed files with 74 additions and 64 deletions
+2 -2
View File
@@ -284,8 +284,8 @@
"@grafana/plugin-ui": "0.10.5",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "6.10.4",
"@grafana/scenes-react": "6.10.4",
"@grafana/scenes": "^6.12.0",
"@grafana/scenes-react": "^6.12.0",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
@@ -144,6 +144,9 @@ const dashboard = {
to: 'now',
},
timepicker: { refresh_intervals: ['5s', '30s', '1m'] },
templating: {
list: [],
},
meta: {
canSave: true,
folderId: 1,
@@ -5,7 +5,6 @@ import {
sceneGraph,
SceneObjectBase,
SceneObjectState,
SceneScopesBridge,
SceneTimeRangeLike,
VariableDependencyConfig,
} from '@grafana/scenes';
@@ -22,7 +21,6 @@ export interface DashboardReloadBehaviorState extends SceneObjectState {
export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBehaviorState> {
private _timeRange: SceneTimeRangeLike | undefined;
private _scopesBridge: SceneScopesBridge | undefined;
private _dashboardScene: DashboardScene | undefined;
constructor(state: DashboardReloadBehaviorState) {
@@ -40,17 +38,11 @@ export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBeha
}
this._timeRange = sceneGraph.getTimeRange(this);
this._scopesBridge = sceneGraph.getScopesBridge(this);
this._dashboardScene = sceneGraph.getAncestor(this, DashboardScene);
this._variableDependency = new VariableDependencyConfig(this, {
onAnyVariableChanged: this.reloadDashboard,
});
this._scopesBridge?.subscribeToValue(() => {
if (shouldReload) {
this.reloadDashboard();
}
dependsOnScopes: true,
});
this._subs.add(
@@ -82,9 +74,11 @@ export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBeha
// This is wrapped in setTimeout in order to allow variables and scopes to be set in the URL before actually reloading the dashboard
setTimeout(() => {
const scopes = sceneGraph.getScopes(this) ?? [];
getDashboardScenePageStateManager().reloadDashboard({
version: this.state.version!,
scopes: this._scopesBridge?.getValue().map((scope) => scope.metadata.name) ?? [],
scopes: scopes.map((scope) => scope.metadata.name),
// We're not using the getUrlState from timeRange since it makes more sense to pass the absolute timestamps as opposed to relative time
timeRange: {
from: this._timeRange!.state.value.from.toISOString(),
@@ -8,7 +8,6 @@ import {
SceneObjectBase,
SceneObjectRef,
SceneObjectState,
SceneScopesBridge,
SceneTimeRange,
sceneUtils,
SceneVariable,
@@ -142,7 +141,6 @@ export interface DashboardSceneState extends SceneObjectState {
panelsPerRow?: number;
/** options pane */
editPane: DashboardEditPane;
scopesBridge: SceneScopesBridge | undefined;
/** Manages dragging/dropping of layout items */
layoutOrchestrator?: DashboardLayoutOrchestrator;
}
@@ -199,7 +197,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
links: state.links ?? [],
...state,
editPane: new DashboardEditPane(),
scopesBridge: config.featureToggles.scopeFilters ? new SceneScopesBridge({}) : undefined,
layoutOrchestrator: new DashboardLayoutOrchestrator(),
});
@@ -212,8 +209,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
}
private _activationHandler() {
this.state.scopesBridge?.setEnabled(true);
let prevSceneContext = window.__grafanaSceneContext;
const isNew = locationService.getLocation().pathname === '/dashboard/new';
@@ -222,7 +217,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
this._initializePanelSearch();
if (this.state.isEditing) {
this.state.scopesBridge?.setReadOnly(true);
this._initialUrlState = locationService.getLocation();
this._changeTracker.startTrackingChanges();
}
@@ -247,8 +241,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
// Deactivation logic
return () => {
this.state.scopesBridge?.setReadOnly(false);
this.state.scopesBridge?.setEnabled(false);
window.__grafanaSceneContext = prevSceneContext;
clearKeyBindings();
this._changeTracker.terminate();
@@ -281,9 +273,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
// Propagate change edit mode change to children
this.state.body.editModeChanged?.(true);
// Propagate edit mode to scopes
this.state.scopesBridge?.setReadOnly(true);
this._changeTracker.startTrackingChanges();
};
@@ -324,7 +313,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
if (!this.state.isDirty || skipConfirm) {
this.exitEditModeConfirmed(restoreInitialState || this.state.isDirty);
this.state.scopesBridge?.setReadOnly(false);
return;
}
@@ -336,7 +324,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
yesText: 'Discard',
onConfirm: () => {
this.exitEditModeConfirmed();
this.state.scopesBridge?.setReadOnly(false);
},
})
);
@@ -1,7 +1,8 @@
import { useEffect, useMemo } from 'react';
import { useContext, useEffect, useMemo } from 'react';
import { useLocation, useParams } from 'react-router-dom-v5-compat';
import { PageLayoutType } from '@grafana/data';
import { ScopesContext } from '@grafana/runtime';
import { SceneComponentProps } from '@grafana/scenes';
import { Page } from 'app/core/components/Page/Page';
import { getNavModel } from 'app/core/selectors/navModel';
@@ -22,11 +23,11 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
panelSearch,
panelsPerRow,
isEditing,
scopesBridge,
layoutOrchestrator,
} = model.useState();
const { type } = useParams();
const location = useLocation();
const scopesContext = useContext(ScopesContext);
const navIndex = useSelector((state) => state.navIndex);
const pageNav = model.getPageNav(location, navIndex);
const bodyToRender = model.getBodyToRender();
@@ -47,10 +48,21 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
}
}, [isSettingsOpen, editPanel, viewPanelScene, model]);
useEffect(() => {
if (scopesContext && isEditing) {
scopesContext.setReadOnly(true);
return () => {
scopesContext.setReadOnly(false);
};
}
return;
}, [scopesContext, isEditing]);
if (editview) {
return (
<>
{scopesBridge && <scopesBridge.Component model={scopesBridge} />}
<editview.Component model={editview} />
{overlay && <overlay.Component model={overlay} />}
</>
@@ -69,7 +81,6 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
<>
{layoutOrchestrator && <layoutOrchestrator.Component model={layoutOrchestrator} />}
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
{scopesBridge && <scopesBridge.Component model={scopesBridge} />}
{editPanel && <editPanel.Component model={editPanel} />}
{!editPanel && (
<DashboardEditPaneSplitter
@@ -141,8 +141,6 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
this._scene.onEnterEditMode();
}
this._scene.state.scopesBridge?.setReadOnly(true);
const libPanelBehavior = getLibraryPanelBehavior(panel);
if (libPanelBehavior && !libPanelBehavior?.state.isLoaded) {
this._waitForLibPanelToLoadBeforeEnteringPanelEdit(panel, libPanelBehavior);
@@ -29,6 +29,10 @@ export function VariableValueSelectWrapper({ variable }: VariableSelectProps) {
const styles = useStyles2(getStyles);
if (state.hide === VariableHide.hideVariable) {
if (variable.UNSAFE_renderAsHidden) {
return <variable.Component model={variable} />;
}
return null;
}
@@ -4,6 +4,7 @@ import {
MultiValueVariable,
SceneVariables,
sceneUtils,
SceneVariable,
} from '@grafana/scenes';
import {
VariableModel,
@@ -47,6 +48,7 @@ import {
export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptions?: boolean) {
const variables: VariableModel[] = [];
for (const variable of set.state.variables) {
const commonProperties = {
name: variable.state.name,
@@ -191,6 +193,8 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
filters: validateFiltersOrigin(variable.state.filters),
defaultKeys: variable.state.defaultKeys,
});
} else if (variable.state.type === 'system') {
// Not persisted
} else {
throw new Error('Unsupported variable type');
}
@@ -435,6 +439,8 @@ export function sceneVariablesSetToSchemaV2Variables(
},
};
variables.push(adhocVariable);
} else if (variable.state.type === 'system') {
// Do nothing
} else {
throw new Error('Unsupported variable type: ' + variable.state.type);
}
@@ -459,3 +465,7 @@ function validateFiltersOrigin(filters?: SceneAdHocFilterWithLabels[]): AdHocFil
}) || []
);
}
export function isVariableEditable(variable: SceneVariable) {
return variable.state.type !== 'system';
}
@@ -15,6 +15,7 @@ import {
SceneTimeRange,
SceneVariable,
SceneVariableSet,
ScopesVariable,
TextBoxVariable,
} from '@grafana/scenes';
import {
@@ -227,17 +228,10 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
function getVariables(dashboard: DashboardV2Spec, isSnapshot: boolean): SceneVariableSet | undefined {
let variables: SceneVariableSet | undefined;
if (dashboard.variables.length) {
if (isSnapshot) {
variables = createVariablesForSnapshot(dashboard);
} else {
variables = createVariablesForDashboard(dashboard);
}
if (isSnapshot) {
variables = createVariablesForSnapshot(dashboard);
} else {
// Create empty variable set
variables = new SceneVariableSet({
variables: [],
});
variables = createVariablesForDashboard(dashboard);
}
return variables;
@@ -257,6 +251,10 @@ function createVariablesForDashboard(dashboard: DashboardV2Spec) {
// Added temporarily to allow skipping non-compatible variables
.filter((v): v is SceneVariable => Boolean(v));
if (config.featureToggles.scopeFilters) {
variableObjects.push(new ScopesVariable({ enable: true }));
}
return new SceneVariableSet({
variables: variableObjects,
});
@@ -181,17 +181,10 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
const uid = oldModel.uid;
const serializerVersion = config.featureToggles.dashboardNewLayouts ? 'v2' : 'v1';
if (oldModel.templating?.list?.length) {
if (oldModel.meta.isSnapshot) {
variables = createVariablesForSnapshot(oldModel);
} else {
variables = createVariablesForDashboard(oldModel);
}
if (oldModel.meta.isSnapshot) {
variables = createVariablesForSnapshot(oldModel);
} else {
// Create empty variable set
variables = new SceneVariableSet({
variables: [],
});
variables = createVariablesForDashboard(oldModel);
}
if (oldModel.annotations?.list?.length && !oldModel.isSnapshot()) {
@@ -9,6 +9,7 @@ import { reportInteraction } from '@grafana/runtime';
import { SceneVariable, SceneVariableState } from '@grafana/scenes';
import { useStyles2, Stack, Button, EmptyState, TextLink } from '@grafana/ui';
import { isVariableEditable } from '../../serialization/sceneVariablesSetToVariables';
import { VariablesDependenciesButton } from '../../variables/VariablesDependenciesButton';
import { UsagesToNetwork, VariableUsageTree } from '../../variables/utils';
@@ -46,7 +47,9 @@ export function VariableEditorList({
onChangeOrder(result.source.index, result.destination.index);
};
return variables.length <= 0 ? (
const editableVariables = variables.filter(isVariableEditable);
return editableVariables.length <= 0 ? (
<EmptyVariablesList onAdd={onAdd} />
) : (
<Stack direction="column" gap={3}>
@@ -71,6 +74,10 @@ export function VariableEditorList({
{(provided) => (
<tbody ref={provided.innerRef} {...provided.droppableProps}>
{variables.map((variableScene, index) => {
if (!isVariableEditable(variableScene)) {
return null;
}
const variableState = variableScene.state;
return (
<VariableEditorListRow
@@ -10,6 +10,7 @@ import {
QueryVariable,
SceneVariable,
SceneVariableSet,
ScopesVariable,
TextBoxVariable,
} from '@grafana/scenes';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
@@ -34,6 +35,10 @@ export function createVariablesForDashboard(oldModel: DashboardModel) {
// Added temporarily to allow skipping non-compatible variables
.filter((v): v is SceneVariable => Boolean(v));
if (config.featureToggles.scopeFilters) {
variableObjects.push(new ScopesVariable({ enable: true }));
}
return new SceneVariableSet({
variables: variableObjects,
});
@@ -197,7 +197,7 @@ export async function renderDashboard(
</KBarProvider>
);
await waitFor(() => expect(sceneGraph.getScopesBridge(scene)).toBeDefined());
await waitFor(() => expect(sceneGraph.getScopes(scene)).toBeDefined());
return {
scene,
+11 -11
View File
@@ -3474,11 +3474,11 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/scenes-react@npm:6.10.4":
version: 6.10.4
resolution: "@grafana/scenes-react@npm:6.10.4"
"@grafana/scenes-react@npm:^6.12.0":
version: 6.12.0
resolution: "@grafana/scenes-react@npm:6.12.0"
dependencies:
"@grafana/scenes": "npm:6.10.4"
"@grafana/scenes": "npm:6.12.0"
lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0"
peerDependencies:
@@ -3490,13 +3490,13 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
checksum: 10/73aee4eca47a27e1fbb7a8f09ddf6a365b121cb361b01c7daabf698677f192c188de0dd64a424db4195139f625660f40371619abbb95a999e14c94e313e25755
checksum: 10/56113394d169c96537a46ca5dc4c62eda54c6addd348c9e3321a122a6d01c19a14c57b04945ee3d836638f3dc1c1941f7647fa649495226432664e0ff8bd0af0
languageName: node
linkType: hard
"@grafana/scenes@npm:6.10.4":
version: 6.10.4
resolution: "@grafana/scenes@npm:6.10.4"
"@grafana/scenes@npm:6.12.0, @grafana/scenes@npm:^6.12.0":
version: 6.12.0
resolution: "@grafana/scenes@npm:6.12.0"
dependencies:
"@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16"
@@ -3514,7 +3514,7 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
checksum: 10/ffd51ad71fe3b89c3cd16bfaa0f11e003bf11e4a4fb7513c5c55fdc5e569f51dcf9fb70cb125e63b5b2269d68e86a961769e07a8078cad70163de2c2ed779ef7
checksum: 10/1bcddada8bf626d552aaa29ae4f916a50968bb36761285b0517500f50d8f52314148a8ed73419f381f8e5b901308cdf2450f0b82754691af512a2cff9f52b6bb
languageName: node
linkType: hard
@@ -17717,8 +17717,8 @@ __metadata:
"@grafana/plugin-ui": "npm:0.10.5"
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/scenes": "npm:6.10.4"
"@grafana/scenes-react": "npm:6.10.4"
"@grafana/scenes": "npm:^6.12.0"
"@grafana/scenes-react": "npm:^6.12.0"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/test-utils": "workspace:*"