DashboardScene: Backward compatability with getDashboardSrv.getCurrent and DashboardModel props and functions (#76371)

* DashboardSrv.getCurrent backward compatibility

* fixes
This commit is contained in:
Torkel Ödegaard 2023-10-13 16:24:04 +02:00 committed by GitHub
parent b01cbc7aef
commit a42040a59a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 236 additions and 28 deletions

View File

@ -37,7 +37,7 @@ export function getAngularPanelReactWrapper(plugin: PanelPlugin): ComponentType<
// @ts-ignore
panel: fakePanel,
// @ts-ignore
dashboard: new DashboardModelCompatibilityWrapper(),
dashboard: getDashboardSrv().getCurrent(),
size: { width: props.width, height: props.height },
queryRunner: queryRunner,
};

View File

@ -1,9 +1,19 @@
import { CoreApp } from '@grafana/data';
import { sceneGraph, SceneGridItem, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardScene } from './DashboardScene';
describe('DashboardScene', () => {
describe('DashboardSrv.getCurrent compatibility', () => {
it('Should set to compatibility wrapper', () => {
const scene = buildTestScene();
scene.activate();
expect(getDashboardSrv().getCurrent()?.uid).toBe('dash-1');
});
});
describe('Editing and discarding', () => {
describe('Given scene in edit mode', () => {
let scene: DashboardScene;

View File

@ -14,10 +14,12 @@ import {
SceneObjectStateChangedEvent,
sceneUtils,
} from '@grafana/scenes';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardMeta } from 'app/types';
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
import { SaveDashboardDrawer } from '../serialization/SaveDashboardDrawer';
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
import {
findVizPanelByKey,
forceRenderChildren,
@ -29,12 +31,21 @@ import {
import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
export interface DashboardSceneState extends SceneObjectState {
/** The title */
title: string;
/** A uid when saved */
uid?: string;
/** @deprecated */
id?: number | null;
/** Layout of panels */
body: SceneObject;
/** NavToolbar actions */
actions?: SceneObject[];
/** Fixed row at the top of the canvas with for example variables and time range controls */
controls?: SceneObject[];
/** True when editing */
isEditing?: boolean;
/** True when user made a change */
isDirty?: boolean;
/** meta flags */
meta: DashboardMeta;
@ -84,11 +95,17 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
this.startTrackingChanges();
}
const oldDashboardWrapper = new DashboardModelCompatibilityWrapper(this);
// @ts-expect-error
getDashboardSrv().setCurrent(oldDashboardWrapper);
// Deactivation logic
return () => {
window.__grafanaSceneContext = undefined;
this.stopTrackingChanges();
this.stopUrlSync();
oldDashboardWrapper.destroy();
};
}

View File

@ -22,6 +22,7 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
if (uid) {
toolbarActions.push(
<DashNavButton
key="share-dashboard-button"
tooltip={t('dashboard.toolbar.share', 'Share dashboard')}
icon="share-alt"
iconSize="lg"
@ -33,7 +34,7 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
toolbarActions.push(
<DashNavButton
key="button-scenes"
key="view-in-old-dashboard-button"
tooltip={'View as dashboard'}
icon="apps"
onClick={() => locationService.push(`/d/${uid}`)}

View File

@ -308,6 +308,7 @@ exports[`transformSceneToSaveModel Given a simple scene Should transform back to
"editable": true,
"fiscalYearStartMonth": 1,
"graphTooltip": 0,
"id": 1351,
"links": [],
"panels": [
{

View File

@ -32,7 +32,6 @@ import {
SceneDataLayerControls,
AdHocFilterSet,
} from '@grafana/scenes';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardDTO } from 'app/types';
@ -61,9 +60,6 @@ export function transformSaveModelToScene(rsp: DashboardDTO): DashboardScene {
autoMigrateOldPanels: false,
});
// Setting for built-in annotations query to run
getDashboardSrv().setCurrent(oldModel);
return createDashboardSceneFromDashboardModel(oldModel);
}
@ -215,6 +211,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
return new DashboardScene({
title: oldModel.title,
uid: oldModel.uid,
id: oldModel.id,
meta: oldModel.meta,
body: new SceneGridLayout({
isLazy: true,

View File

@ -92,6 +92,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
...defaultDashboard,
title: state.title,
uid: state.uid,
id: state.id,
time: {
from: timeRange.from,
to: timeRange.to,

View File

@ -0,0 +1,85 @@
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
import { behaviors, SceneGridItem, SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel } from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema';
import { DashboardScene } from '../scene/DashboardScene';
import { DashboardModelCompatibilityWrapper } from './DashboardModelCompatibilityWrapper';
describe('DashboardModelCompatibilityWrapper', () => {
it('Provide basic prop and function of compatability', () => {
const { wrapper } = setup();
expect(wrapper.uid).toBe('dash-1');
expect(wrapper.title).toBe('hello');
expect(wrapper.time.from).toBe('now-6h');
});
it('Shared tooltip functions', () => {
const { scene, wrapper } = setup();
expect(wrapper.sharedTooltipModeEnabled()).toBe(false);
expect(wrapper.sharedCrosshairModeOnly()).toBe(false);
scene.setState({ $behaviors: [new behaviors.CursorSync({ sync: DashboardCursorSync.Crosshair })] });
expect(wrapper.sharedTooltipModeEnabled()).toBe(true);
expect(wrapper.sharedCrosshairModeOnly()).toBe(true);
});
it('Get timezone from time range', () => {
const { wrapper } = setup();
expect(wrapper.getTimezone()).toBe('America/New_York');
});
it('Should emit TimeRangeUpdatedEvent when time range change', () => {
const { scene, wrapper } = setup();
let timeChanged = 0;
wrapper.events.subscribe(TimeRangeUpdatedEvent, () => timeChanged++);
scene.state.$timeRange!.onRefresh();
expect(timeChanged).toBe(1);
});
it('Can get fake panel with getPanelById', () => {
const { wrapper } = setup();
expect(wrapper.getPanelById(1)!.title).toBe('Panel A');
expect(wrapper.getPanelById(2)!.title).toBe('Panel B');
});
});
function setup() {
const scene = new DashboardScene({
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({
timeZone: 'America/New_York',
}),
body: new SceneGridLayout({
children: [
new SceneGridItem({
key: 'griditem-1',
x: 0,
body: new VizPanel({
title: 'Panel A',
key: 'panel-1',
pluginId: 'table',
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel B',
key: 'panel-2',
pluginId: 'table',
}),
}),
],
}),
});
const wrapper = new DashboardModelCompatibilityWrapper(scene);
return { scene, wrapper };
}

View File

@ -1,17 +1,66 @@
import { DashboardCursorSync, dateTimeFormat, DateTimeInput, EventBusSrv } from '@grafana/data';
import { behaviors, sceneGraph } from '@grafana/scenes';
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 { DashboardScene } from '../scene/DashboardScene';
import { findVizPanelByKey, getVizPanelKeyForPanelId } from './utils';
/**
* Will move this to make it the main way we remain somewhat compatible with getDashboardSrv().getCurrent
*/
export class DashboardModelCompatibilityWrapper {
events = new EventBusSrv();
panelInitialized() {}
public events = new EventBusSrv();
private _subs = new Subscription();
public constructor(private _scene: DashboardScene) {
const timeRange = sceneGraph.getTimeRange(_scene);
this._subs.add(
timeRange.subscribeToState((state, prev) => {
if (state.value !== prev.value) {
this.events.publish(new TimeRangeUpdatedEvent(state.value));
}
})
);
}
public get id(): number | null {
return this._scene.state.id ?? null;
}
public get uid() {
return this._scene.state.uid ?? null;
}
public get title() {
return this._scene.state.title;
}
public get meta() {
return this._scene.state.meta;
}
public get time() {
const time = sceneGraph.getTimeRange(this._scene);
return {
from: time.state.from,
to: time.state.to,
};
}
/**
* Used from from timeseries migration handler to migrate time regions to dashboard annotations
*/
public get annotations(): { list: AnnotationQuery[] } {
console.error('Scenes DashboardModelCompatibilityWrapper.annotations not implemented (yet)');
return { list: [] };
}
public getTimezone() {
const time = sceneGraph.getTimeRange(window.__grafanaSceneContext);
const time = sceneGraph.getTimeRange(this._scene);
return time.getTimeZone();
}
@ -20,16 +69,14 @@ export class DashboardModelCompatibilityWrapper {
}
public sharedCrosshairModeOnly() {
return this._getSyncMode() > 1;
return this._getSyncMode() === 1;
}
private _getSyncMode() {
const dashboard = this.getDashboardScene();
if (dashboard.state.$behaviors) {
for (const behavior of dashboard.state.$behaviors) {
if (this._scene.state.$behaviors) {
for (const behavior of this._scene.state.$behaviors) {
if (behavior instanceof behaviors.CursorSync) {
return behavior.state.sync > 0;
return behavior.state.sync;
}
}
}
@ -37,14 +84,6 @@ export class DashboardModelCompatibilityWrapper {
return DashboardCursorSync.Off;
}
private getDashboardScene(): DashboardScene {
if (window.__grafanaSceneContext instanceof DashboardScene) {
return window.__grafanaSceneContext;
}
throw new Error('Dashboard scene not found');
}
public otherPanelInFullscreen(panel: unknown) {
return false;
}
@ -55,4 +94,62 @@ export class DashboardModelCompatibilityWrapper {
timeZone: this.getTimezone(),
});
}
public getPanelById(id: number): PanelCompatibilityWrapper | null {
const vizPanel = findVizPanelByKey(this._scene, getVizPanelKeyForPanelId(id));
if (vizPanel) {
return new PanelCompatibilityWrapper(vizPanel);
}
return null;
}
public removePanel(panel: PanelCompatibilityWrapper) {
// TODO
console.error('Scenes DashboardModelCompatibilityWrapper.removePanel not implemented (yet)');
}
public canEditAnnotations(dashboardUID?: string) {
// TOOD
return false;
}
public panelInitialized() {}
public destroy() {
this.events.removeAllListeners();
this._subs.unsubscribe();
}
}
class PanelCompatibilityWrapper {
constructor(private _vizPanel: VizPanel) {}
public get type() {
return this._vizPanel.state.pluginId;
}
public get title() {
return this._vizPanel.state.title;
}
public get transformations() {
if (this._vizPanel.state.$data instanceof SceneDataTransformer) {
return this._vizPanel.state.$data.state.transformations;
}
return [];
}
public refresh() {
console.error('Scenes PanelCompatibilityWrapper.refresh no implemented (yet)');
}
public render() {
console.error('Scenes PanelCompatibilityWrapper.render no implemented (yet)');
}
public getQueryRunner() {
console.error('Scenes PanelCompatibilityWrapper.getQueryRunner no implemented (yet)');
}
}

View File

@ -4,7 +4,7 @@ import React, { useState } from 'react';
import { useAsync } from 'react-use';
import { dateMath, dateTime, GrafanaTheme2, PanelProps } from '@grafana/data';
import { getBackendSrv, getTemplateSrv } from '@grafana/runtime';
import { getBackendSrv } from '@grafana/runtime';
import { Card, CustomScrollbar, Icon, useStyles2 } from '@grafana/ui';
import alertDef from 'app/features/alerting/state/alertDef';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
@ -23,10 +23,9 @@ export function AlertList(props: PanelProps<AlertListOptions>) {
const params: any = {
state: getStateFilter(props.options.stateFilter),
};
const panel = getDashboardSrv().getCurrent()?.getPanelById(props.id)!;
if (props.options.alertName) {
params.query = getTemplateSrv().replace(props.options.alertName, panel.scopedVars);
params.query = props.replaceVariables(props.options.alertName);
}
if (props.options.folderId >= 0) {