mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Support time region migrations (#84147)
* DashboardScene: Support time region migrations * Update * Update * Update * fix * Fix lock * fix tests * Fix migrations test --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
parent
e5d1cd8ea5
commit
d290aaff46
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SceneDataLayerProviderState,
|
||||||
|
SceneDataLayerProvider,
|
||||||
|
SceneDataLayerSetBase,
|
||||||
|
SceneComponentProps,
|
||||||
|
} from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { AlertStatesDataLayer } from './AlertStatesDataLayer';
|
||||||
|
|
||||||
|
export interface DashboardDataLayerSetState extends SceneDataLayerProviderState {
|
||||||
|
alertStatesLayer?: AlertStatesDataLayer;
|
||||||
|
annotationLayers: SceneDataLayerProvider[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardDataLayerSet
|
||||||
|
extends SceneDataLayerSetBase<DashboardDataLayerSetState>
|
||||||
|
implements SceneDataLayerProvider
|
||||||
|
{
|
||||||
|
public constructor(state: Partial<DashboardDataLayerSetState>) {
|
||||||
|
super({
|
||||||
|
...state,
|
||||||
|
name: state.name ?? 'Data layers',
|
||||||
|
annotationLayers: state.annotationLayers ?? [],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addActivationHandler(() => this._onActivate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onActivate() {
|
||||||
|
this._subs.add(
|
||||||
|
this.subscribeToState((newState, oldState) => {
|
||||||
|
if (newState.annotationLayers !== oldState.annotationLayers) {
|
||||||
|
this.querySub?.unsubscribe();
|
||||||
|
this.subscribeToAllLayers(this.getAllLayers());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subscribeToAllLayers(this.getAllLayers());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.querySub?.unsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public addAnnotationLayer(layer: SceneDataLayerProvider) {
|
||||||
|
this.setState({ annotationLayers: [...this.state.annotationLayers, layer] });
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAllLayers() {
|
||||||
|
const layers = [...this.state.annotationLayers];
|
||||||
|
|
||||||
|
if (this.state.alertStatesLayer) {
|
||||||
|
layers.push(this.state.alertStatesLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component = ({ model }: SceneComponentProps<DashboardDataLayerSet>) => {
|
||||||
|
const { annotationLayers } = model.useState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{annotationLayers.map((layer) => (
|
||||||
|
<layer.Component model={layer} key={layer.state.key} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
import { AnnotationChangeEvent, AnnotationEventUIModel, CoreApp, DataFrame } from '@grafana/data';
|
import { AnnotationChangeEvent, AnnotationEventUIModel, CoreApp, DataFrame } from '@grafana/data';
|
||||||
import { AdHocFiltersVariable, dataLayers, SceneDataLayerSet, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes';
|
import { AdHocFiltersVariable, dataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes';
|
||||||
import { DataSourceRef } from '@grafana/schema';
|
import { DataSourceRef } from '@grafana/schema';
|
||||||
import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
||||||
import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api';
|
import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api';
|
||||||
|
|
||||||
|
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||||
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
||||||
|
|
||||||
import { DashboardScene } from './DashboardScene';
|
import { DashboardScene } from './DashboardScene';
|
||||||
@ -128,13 +129,13 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getBuiltInAnnotationsLayer(scene: DashboardScene): dataLayers.AnnotationsDataLayer | undefined {
|
function getBuiltInAnnotationsLayer(scene: DashboardScene): dataLayers.AnnotationsDataLayer | undefined {
|
||||||
|
const set = dashboardSceneGraph.getDataLayers(scene);
|
||||||
// When there is no builtin annotations query we disable the ability to add annotations
|
// When there is no builtin annotations query we disable the ability to add annotations
|
||||||
if (scene.state.$data instanceof SceneDataLayerSet) {
|
|
||||||
for (const layer of scene.state.$data.state.layers) {
|
for (const layer of set.state.annotationLayers) {
|
||||||
if (layer instanceof dataLayers.AnnotationsDataLayer) {
|
if (layer instanceof dataLayers.AnnotationsDataLayer) {
|
||||||
if (layer.state.isEnabled && layer.state.query.builtIn) {
|
if (layer.state.isEnabled && layer.state.query.builtIn) {
|
||||||
return layer;
|
return layer;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
GroupByVariable,
|
GroupByVariable,
|
||||||
QueryVariable,
|
QueryVariable,
|
||||||
SceneDataLayerControls,
|
SceneDataLayerControls,
|
||||||
SceneDataLayerSet,
|
|
||||||
SceneDataTransformer,
|
SceneDataTransformer,
|
||||||
SceneGridLayout,
|
SceneGridLayout,
|
||||||
SceneGridRow,
|
SceneGridRow,
|
||||||
@ -42,6 +41,7 @@ import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard
|
|||||||
import { DashboardDataDTO } from 'app/types';
|
import { DashboardDataDTO } from 'app/types';
|
||||||
|
|
||||||
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||||
@ -1230,26 +1230,26 @@ describe('transformSaveModelToScene', () => {
|
|||||||
it('Should build correct scene model', () => {
|
it('Should build correct scene model', () => {
|
||||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||||
|
|
||||||
expect(scene.state.$data).toBeInstanceOf(SceneDataLayerSet);
|
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
|
||||||
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
|
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
|
||||||
|
|
||||||
const dataLayers = scene.state.$data as SceneDataLayerSet;
|
const dataLayers = scene.state.$data as DashboardDataLayerSet;
|
||||||
expect(dataLayers.state.layers).toHaveLength(4);
|
expect(dataLayers.state.annotationLayers).toHaveLength(4);
|
||||||
expect(dataLayers.state.layers[0].state.name).toBe('Annotations & Alerts');
|
expect(dataLayers.state.annotationLayers[0].state.name).toBe('Annotations & Alerts');
|
||||||
expect(dataLayers.state.layers[0].state.isEnabled).toBe(true);
|
expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(true);
|
||||||
expect(dataLayers.state.layers[0].state.isHidden).toBe(false);
|
expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(false);
|
||||||
|
|
||||||
expect(dataLayers.state.layers[1].state.name).toBe('Enabled');
|
expect(dataLayers.state.annotationLayers[1].state.name).toBe('Enabled');
|
||||||
expect(dataLayers.state.layers[1].state.isEnabled).toBe(true);
|
expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(true);
|
||||||
expect(dataLayers.state.layers[1].state.isHidden).toBe(false);
|
expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(false);
|
||||||
|
|
||||||
expect(dataLayers.state.layers[2].state.name).toBe('Disabled');
|
expect(dataLayers.state.annotationLayers[2].state.name).toBe('Disabled');
|
||||||
expect(dataLayers.state.layers[2].state.isEnabled).toBe(false);
|
expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(false);
|
||||||
expect(dataLayers.state.layers[2].state.isHidden).toBe(false);
|
expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(false);
|
||||||
|
|
||||||
expect(dataLayers.state.layers[3].state.name).toBe('Hidden');
|
expect(dataLayers.state.annotationLayers[3].state.name).toBe('Hidden');
|
||||||
expect(dataLayers.state.layers[3].state.isEnabled).toBe(true);
|
expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(true);
|
||||||
expect(dataLayers.state.layers[3].state.isHidden).toBe(true);
|
expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1258,12 +1258,11 @@ describe('transformSaveModelToScene', () => {
|
|||||||
config.unifiedAlertingEnabled = true;
|
config.unifiedAlertingEnabled = true;
|
||||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||||
|
|
||||||
expect(scene.state.$data).toBeInstanceOf(SceneDataLayerSet);
|
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
|
||||||
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
|
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
|
||||||
|
|
||||||
const dataLayers = scene.state.$data as SceneDataLayerSet;
|
const dataLayers = scene.state.$data as DashboardDataLayerSet;
|
||||||
expect(dataLayers.state.layers).toHaveLength(5);
|
expect(dataLayers.state.alertStatesLayer).toBeDefined();
|
||||||
expect(dataLayers.state.layers[4].state.name).toBe('Alert States');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should add alert states data layer if any panel has a legacy alert defined', () => {
|
it('Should add alert states data layer if any panel has a legacy alert defined', () => {
|
||||||
@ -1272,12 +1271,11 @@ describe('transformSaveModelToScene', () => {
|
|||||||
dashboard.panels![0].alert = {};
|
dashboard.panels![0].alert = {};
|
||||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||||
|
|
||||||
expect(scene.state.$data).toBeInstanceOf(SceneDataLayerSet);
|
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
|
||||||
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
|
expect(scene.state.controls!.state.variableControls[1]).toBeInstanceOf(SceneDataLayerControls);
|
||||||
|
|
||||||
const dataLayers = scene.state.$data as SceneDataLayerSet;
|
const dataLayers = scene.state.$data as DashboardDataLayerSet;
|
||||||
expect(dataLayers.state.layers).toHaveLength(5);
|
expect(dataLayers.state.alertStatesLayer).toBeDefined();
|
||||||
expect(dataLayers.state.layers[4].state.name).toBe('Alert States');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
behaviors,
|
behaviors,
|
||||||
VizPanelState,
|
VizPanelState,
|
||||||
SceneGridItemLike,
|
SceneGridItemLike,
|
||||||
SceneDataLayerSet,
|
|
||||||
SceneDataLayerProvider,
|
SceneDataLayerProvider,
|
||||||
SceneDataLayerControls,
|
SceneDataLayerControls,
|
||||||
TextBoxVariable,
|
TextBoxVariable,
|
||||||
@ -37,6 +36,7 @@ import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
|||||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||||
import { DashboardControls } from '../scene/DashboardControls';
|
import { DashboardControls } from '../scene/DashboardControls';
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
|
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
|
||||||
import { registerDashboardMacro } from '../scene/DashboardMacro';
|
import { registerDashboardMacro } from '../scene/DashboardMacro';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
@ -216,8 +216,9 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]):
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
|
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
|
||||||
let variables: SceneVariableSet | undefined = undefined;
|
let variables: SceneVariableSet | undefined;
|
||||||
let layers: SceneDataLayerProvider[] = [];
|
let annotationLayers: SceneDataLayerProvider[] = [];
|
||||||
|
let alertStatesLayer: AlertStatesDataLayer | undefined;
|
||||||
|
|
||||||
if (oldModel.templating?.list?.length) {
|
if (oldModel.templating?.list?.length) {
|
||||||
const variableObjects = oldModel.templating.list
|
const variableObjects = oldModel.templating.list
|
||||||
@ -244,7 +245,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oldModel.annotations?.list?.length && !oldModel.isSnapshot()) {
|
if (oldModel.annotations?.list?.length && !oldModel.isSnapshot()) {
|
||||||
layers = oldModel.annotations?.list.map((a) => {
|
annotationLayers = oldModel.annotations?.list.map((a) => {
|
||||||
// Each annotation query is an individual data layer
|
// Each annotation query is an individual data layer
|
||||||
return new DashboardAnnotationsDataLayer({
|
return new DashboardAnnotationsDataLayer({
|
||||||
key: `annotations-${a.name}`,
|
key: `annotations-${a.name}`,
|
||||||
@ -264,12 +265,10 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldUseAlertStatesLayer) {
|
if (shouldUseAlertStatesLayer) {
|
||||||
layers.push(
|
alertStatesLayer = new AlertStatesDataLayer({
|
||||||
new AlertStatesDataLayer({
|
key: 'alert-states',
|
||||||
key: 'alert-states',
|
name: 'Alert States',
|
||||||
name: 'Alert States',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dashboardScene = new DashboardScene({
|
const dashboardScene = new DashboardScene({
|
||||||
@ -307,12 +306,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
|||||||
trackIfIsEmpty,
|
trackIfIsEmpty,
|
||||||
new behaviors.LiveNowTimer(oldModel.liveNow),
|
new behaviors.LiveNowTimer(oldModel.liveNow),
|
||||||
],
|
],
|
||||||
$data:
|
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
|
||||||
layers.length > 0
|
|
||||||
? new SceneDataLayerSet({
|
|
||||||
layers,
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
controls: new DashboardControls({
|
controls: new DashboardControls({
|
||||||
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
|
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
|
||||||
timePicker: new SceneTimePicker({}),
|
timePicker: new SceneTimePicker({}),
|
||||||
|
@ -16,13 +16,14 @@ import {
|
|||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||||
import { getPluginLinkExtensions, setPluginImportUtils } from '@grafana/runtime';
|
import { getPluginLinkExtensions, setPluginImportUtils } from '@grafana/runtime';
|
||||||
import { MultiValueVariable, SceneDataLayerSet, SceneGridLayout, SceneGridRow, VizPanel } from '@grafana/scenes';
|
import { MultiValueVariable, SceneGridLayout, SceneGridRow, VizPanel } from '@grafana/scenes';
|
||||||
import { Dashboard, LoadingState, Panel, RowPanel, VariableRefresh } from '@grafana/schema';
|
import { Dashboard, LoadingState, Panel, RowPanel, VariableRefresh } from '@grafana/schema';
|
||||||
import { PanelModel } from 'app/features/dashboard/state';
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
|
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
|
||||||
import { reduceTransformRegistryItem } from 'app/features/transformers/editors/ReduceTransformerEditor';
|
import { reduceTransformRegistryItem } from 'app/features/transformers/editors/ReduceTransformerEditor';
|
||||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
||||||
|
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||||
@ -402,10 +403,11 @@ describe('transformSceneToSaveModel', () => {
|
|||||||
expect(saveModel.annotations?.list?.length).toBe(4);
|
expect(saveModel.annotations?.list?.length).toBe(4);
|
||||||
expect(saveModel.annotations?.list).toMatchSnapshot();
|
expect(saveModel.annotations?.list).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transform annotations to save model after state changes', () => {
|
it('should transform annotations to save model after state changes', () => {
|
||||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
|
||||||
|
|
||||||
const layers = (scene.state.$data as SceneDataLayerSet)?.state.layers;
|
const layers = (scene.state.$data as DashboardDataLayerSet)?.state.annotationLayers;
|
||||||
const enabledLayer = layers[1];
|
const enabledLayer = layers[1];
|
||||||
const hiddenLayer = layers[3];
|
const hiddenLayer = layers[3];
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
SceneDataTransformer,
|
SceneDataTransformer,
|
||||||
SceneVariableSet,
|
SceneVariableSet,
|
||||||
LocalValueVariable,
|
LocalValueVariable,
|
||||||
SceneDataLayerSet,
|
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import {
|
import {
|
||||||
AnnotationQuery,
|
AnnotationQuery,
|
||||||
@ -33,6 +32,7 @@ import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/Dashboard
|
|||||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||||
|
|
||||||
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
@ -77,10 +77,9 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
|
|||||||
}
|
}
|
||||||
|
|
||||||
let annotations: AnnotationQuery[] = [];
|
let annotations: AnnotationQuery[] = [];
|
||||||
if (data instanceof SceneDataLayerSet) {
|
|
||||||
const layers = data.state.layers;
|
|
||||||
|
|
||||||
annotations = dataLayersToAnnotations(layers);
|
if (data instanceof DashboardDataLayerSet) {
|
||||||
|
annotations = dataLayersToAnnotations(data.state.annotationLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variablesSet instanceof SceneVariableSet) {
|
if (variablesSet instanceof SceneVariableSet) {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { map, of } from 'rxjs';
|
import { map, of } from 'rxjs';
|
||||||
|
|
||||||
import { AnnotationQuery, DataQueryRequest, DataSourceApi, LoadingState, PanelData } from '@grafana/data';
|
import { AnnotationQuery, DataQueryRequest, DataSourceApi, LoadingState, PanelData } from '@grafana/data';
|
||||||
import { SceneDataLayerSet, SceneGridLayout, SceneTimeRange, dataLayers } from '@grafana/scenes';
|
import { SceneGridLayout, SceneTimeRange, dataLayers } from '@grafana/scenes';
|
||||||
|
|
||||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
|
||||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||||
import { activateFullSceneTree } from '../utils/test-utils';
|
import { activateFullSceneTree } from '../utils/test-utils';
|
||||||
@ -48,40 +48,26 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
describe('AnnotationsEditView', () => {
|
describe('AnnotationsEditView', () => {
|
||||||
describe('Dashboard annotations state', () => {
|
describe('Dashboard annotations state', () => {
|
||||||
let annotationsView: AnnotationsEditView;
|
let annotationsView: AnnotationsEditView;
|
||||||
let dashboardScene: DashboardScene;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const result = await buildTestScene();
|
const result = await buildTestScene();
|
||||||
annotationsView = result.annotationsView;
|
annotationsView = result.annotationsView;
|
||||||
dashboardScene = result.dashboard;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the correct urlKey', () => {
|
it('should return the correct urlKey', () => {
|
||||||
expect(annotationsView.getUrlKey()).toBe('annotations');
|
expect(annotationsView.getUrlKey()).toBe('annotations');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the annotations length', () => {
|
|
||||||
expect(annotationsView.getAnnotationsLength()).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 0 if no annotations', () => {
|
|
||||||
dashboardScene.setState({
|
|
||||||
$data: new SceneDataLayerSet({ layers: [] }),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(annotationsView.getAnnotationsLength()).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a new annotation and group it with the other annotations', () => {
|
it('should add a new annotation and group it with the other annotations', () => {
|
||||||
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(2);
|
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||||
|
|
||||||
annotationsView.onNew();
|
annotationsView.onNew();
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(3);
|
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||||
expect(dataLayers?.state.layers[1].state.name).toBe(newAnnotationName);
|
expect(dataLayers?.state.annotationLayers[1].state.name).toBe(newAnnotationName);
|
||||||
expect(dataLayers?.state.layers[1].isActive).toBe(true);
|
expect(dataLayers?.state.annotationLayers[1].isActive).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move an annotation up one position', () => {
|
it('should move an annotation up one position', () => {
|
||||||
@ -89,13 +75,13 @@ describe('AnnotationsEditView', () => {
|
|||||||
|
|
||||||
annotationsView.onNew();
|
annotationsView.onNew();
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(3);
|
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe('test');
|
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('test');
|
||||||
|
|
||||||
annotationsView.onMove(1, MoveDirection.UP);
|
annotationsView.onMove(1, MoveDirection.UP);
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(3);
|
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe(newAnnotationName);
|
expect(dataLayers?.state.annotationLayers[0].state.name).toBe(newAnnotationName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move an annotation down one position', () => {
|
it('should move an annotation down one position', () => {
|
||||||
@ -103,33 +89,32 @@ describe('AnnotationsEditView', () => {
|
|||||||
|
|
||||||
annotationsView.onNew();
|
annotationsView.onNew();
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(3);
|
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe('test');
|
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('test');
|
||||||
|
|
||||||
annotationsView.onMove(0, MoveDirection.DOWN);
|
annotationsView.onMove(0, MoveDirection.DOWN);
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(3);
|
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe(newAnnotationName);
|
expect(dataLayers?.state.annotationLayers[0].state.name).toBe(newAnnotationName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete annotation at index', () => {
|
it('should delete annotation at index', () => {
|
||||||
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(2);
|
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||||
|
|
||||||
annotationsView.onDelete(0);
|
annotationsView.onDelete(0);
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(1);
|
expect(dataLayers?.state.annotationLayers.length).toBe(0);
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe('Alert States');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update an annotation at index', () => {
|
it('should update an annotation at index', () => {
|
||||||
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
||||||
|
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe('test');
|
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('test');
|
||||||
|
|
||||||
const annotation: AnnotationQuery = {
|
const annotation: AnnotationQuery = {
|
||||||
...(dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query,
|
...(dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query,
|
||||||
};
|
};
|
||||||
|
|
||||||
annotation.name = 'new name';
|
annotation.name = 'new name';
|
||||||
@ -138,12 +123,16 @@ describe('AnnotationsEditView', () => {
|
|||||||
annotation.iconColor = 'blue';
|
annotation.iconColor = 'blue';
|
||||||
annotationsView.onUpdate(annotation, 0);
|
annotationsView.onUpdate(annotation, 0);
|
||||||
|
|
||||||
expect(dataLayers?.state.layers.length).toBe(2);
|
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||||
expect(dataLayers?.state.layers[0].state.name).toBe('new name');
|
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('new name');
|
||||||
expect((dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query.name).toBe('new name');
|
expect((dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query.name).toBe(
|
||||||
expect((dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query.hide).toBe(true);
|
'new name'
|
||||||
expect((dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query.enable).toBe(false);
|
);
|
||||||
expect((dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query.iconColor).toBe('blue');
|
expect((dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query.hide).toBe(true);
|
||||||
|
expect((dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query.enable).toBe(false);
|
||||||
|
expect((dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query.iconColor).toBe(
|
||||||
|
'blue'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -158,8 +147,8 @@ async function buildTestScene() {
|
|||||||
meta: {
|
meta: {
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
},
|
},
|
||||||
$data: new SceneDataLayerSet({
|
$data: new DashboardDataLayerSet({
|
||||||
layers: [
|
annotationLayers: [
|
||||||
new DashboardAnnotationsDataLayer({
|
new DashboardAnnotationsDataLayer({
|
||||||
key: `annotations-test`,
|
key: `annotations-test`,
|
||||||
query: {
|
query: {
|
||||||
@ -175,10 +164,6 @@ async function buildTestScene() {
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
}),
|
}),
|
||||||
new AlertStatesDataLayer({
|
|
||||||
key: 'alert-states',
|
|
||||||
name: 'Alert States',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
body: new SceneGridLayout({
|
body: new SceneGridLayout({
|
||||||
|
@ -40,7 +40,7 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
|||||||
|
|
||||||
public getDataLayer(editIndex: number): dataLayers.AnnotationsDataLayer {
|
public getDataLayer(editIndex: number): dataLayers.AnnotationsDataLayer {
|
||||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||||
const layer = data.state.layers[editIndex];
|
const layer = data.state.annotationLayers[editIndex];
|
||||||
|
|
||||||
if (!(layer instanceof dataLayers.AnnotationsDataLayer)) {
|
if (!(layer instanceof dataLayers.AnnotationsDataLayer)) {
|
||||||
throw new Error('AnnotationsDataLayer not found at index ' + editIndex);
|
throw new Error('AnnotationsDataLayer not found at index ' + editIndex);
|
||||||
@ -49,12 +49,6 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
|||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAnnotationsLength(): number {
|
|
||||||
return dashboardSceneGraph
|
|
||||||
.getDataLayers(this._dashboard)
|
|
||||||
.state.layers.filter((layer) => layer instanceof DashboardAnnotationsDataLayer).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDashboard(): DashboardScene {
|
public getDashboard(): DashboardScene {
|
||||||
return this._dashboard;
|
return this._dashboard;
|
||||||
}
|
}
|
||||||
@ -76,18 +70,9 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
|||||||
|
|
||||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||||
|
|
||||||
const layers = [...data.state.layers];
|
data.addAnnotationLayer(newAnnotation);
|
||||||
|
|
||||||
//keep annotation layers together
|
this.setState({ editIndex: data.state.annotationLayers.length - 1 });
|
||||||
layers.splice(this.getAnnotationsLength(), 0, newAnnotation);
|
|
||||||
|
|
||||||
data.setState({
|
|
||||||
layers,
|
|
||||||
});
|
|
||||||
|
|
||||||
newAnnotation.activate();
|
|
||||||
|
|
||||||
this.setState({ editIndex: this.getAnnotationsLength() - 1 });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public onEdit = (idx: number) => {
|
public onEdit = (idx: number) => {
|
||||||
@ -100,25 +85,21 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
|||||||
|
|
||||||
public onMove = (idx: number, direction: MoveDirection) => {
|
public onMove = (idx: number, direction: MoveDirection) => {
|
||||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||||
|
const layers = [...data.state.annotationLayers];
|
||||||
|
|
||||||
const layers = [...data.state.layers];
|
|
||||||
const [layer] = layers.splice(idx, 1);
|
const [layer] = layers.splice(idx, 1);
|
||||||
layers.splice(idx + direction, 0, layer);
|
layers.splice(idx + direction, 0, layer);
|
||||||
|
|
||||||
data.setState({
|
data.setState({ annotationLayers: layers });
|
||||||
layers,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDelete = (idx: number) => {
|
public onDelete = (idx: number) => {
|
||||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||||
|
const layers = [...data.state.annotationLayers];
|
||||||
|
|
||||||
const layers = [...data.state.layers];
|
|
||||||
layers.splice(idx, 1);
|
layers.splice(idx, 1);
|
||||||
|
|
||||||
data.setState({
|
data.setState({ annotationLayers: layers });
|
||||||
layers,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public onUpdate = (annotation: AnnotationQuery, editIndex: number) => {
|
public onUpdate = (annotation: AnnotationQuery, editIndex: number) => {
|
||||||
@ -139,14 +120,14 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
|||||||
|
|
||||||
function AnnotationsSettingsView({ model }: SceneComponentProps<AnnotationsEditView>) {
|
function AnnotationsSettingsView({ model }: SceneComponentProps<AnnotationsEditView>) {
|
||||||
const dashboard = model.getDashboard();
|
const dashboard = model.getDashboard();
|
||||||
const { layers } = dashboardSceneGraph.getDataLayers(dashboard).useState();
|
const { annotationLayers } = dashboardSceneGraph.getDataLayers(dashboard).useState();
|
||||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||||
const { editIndex } = model.useState();
|
const { editIndex } = model.useState();
|
||||||
const panels = dashboardSceneGraph.getVizPanels(dashboard);
|
const panels = dashboardSceneGraph.getVizPanels(dashboard);
|
||||||
|
|
||||||
const annotations: AnnotationQuery[] = dataLayersToAnnotations(layers);
|
const annotations: AnnotationQuery[] = dataLayersToAnnotations(annotationLayers);
|
||||||
|
|
||||||
if (editIndex != null && editIndex < model.getAnnotationsLength()) {
|
if (editIndex != null && editIndex < annotationLayers.length) {
|
||||||
return (
|
return (
|
||||||
<AnnotationsSettingsEditView
|
<AnnotationsSettingsEditView
|
||||||
annotationLayer={model.getDataLayer(editIndex)}
|
annotationLayer={model.getDataLayer(editIndex)}
|
||||||
|
@ -134,6 +134,6 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD
|
|||||||
const getStyles = () => ({
|
const getStyles = () => ({
|
||||||
table: css({
|
table: css({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
overflowX: 'scroll',
|
overflowX: 'auto',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
import {
|
import { SceneGridLayout, SceneGridRow, SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes';
|
||||||
SceneDataLayerSet,
|
|
||||||
SceneGridLayout,
|
|
||||||
SceneGridRow,
|
|
||||||
SceneQueryRunner,
|
|
||||||
SceneTimeRange,
|
|
||||||
VizPanel,
|
|
||||||
behaviors,
|
|
||||||
} from '@grafana/scenes';
|
|
||||||
import { DashboardCursorSync } from '@grafana/schema';
|
import { DashboardCursorSync } from '@grafana/schema';
|
||||||
|
|
||||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
|
||||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||||
import { DashboardControls } from '../scene/DashboardControls';
|
import { DashboardControls } from '../scene/DashboardControls';
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
@ -75,8 +67,8 @@ describe('dashboardSceneGraph', () => {
|
|||||||
it('should return the scene data layers', () => {
|
it('should return the scene data layers', () => {
|
||||||
const dataLayers = dashboardSceneGraph.getDataLayers(scene);
|
const dataLayers = dashboardSceneGraph.getDataLayers(scene);
|
||||||
|
|
||||||
expect(dataLayers).toBeInstanceOf(SceneDataLayerSet);
|
expect(dataLayers).toBeInstanceOf(DashboardDataLayerSet);
|
||||||
expect(dataLayers?.state.layers.length).toBe(2);
|
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if there are no scene data layers', () => {
|
it('should throw if there are no scene data layers', () => {
|
||||||
@ -84,7 +76,7 @@ describe('dashboardSceneGraph', () => {
|
|||||||
$data: undefined,
|
$data: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(() => dashboardSceneGraph.getDataLayers(scene)).toThrow('SceneDataLayerSet not found');
|
expect(() => dashboardSceneGraph.getDataLayers(scene)).toThrow('DashboardDataLayerSet not found');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -241,8 +233,8 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
|||||||
sync: DashboardCursorSync.Crosshair,
|
sync: DashboardCursorSync.Crosshair,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
$data: new SceneDataLayerSet({
|
$data: new DashboardDataLayerSet({
|
||||||
layers: [
|
annotationLayers: [
|
||||||
new DashboardAnnotationsDataLayer({
|
new DashboardAnnotationsDataLayer({
|
||||||
key: `annotation`,
|
key: `annotation`,
|
||||||
query: {
|
query: {
|
||||||
@ -255,10 +247,6 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
}),
|
}),
|
||||||
new AlertStatesDataLayer({
|
|
||||||
key: 'alert-states',
|
|
||||||
name: 'Alert States',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
body: new SceneGridLayout({
|
body: new SceneGridLayout({
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { VizPanel, SceneGridRow, SceneDataLayerSet, sceneGraph, SceneGridLayout, behaviors } from '@grafana/scenes';
|
import { VizPanel, SceneGridRow, sceneGraph, SceneGridLayout, behaviors } from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
@ -53,11 +54,11 @@ function getVizPanels(scene: DashboardScene): VizPanel[] {
|
|||||||
return panels;
|
return panels;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataLayers(scene: DashboardScene): SceneDataLayerSet {
|
function getDataLayers(scene: DashboardScene): DashboardDataLayerSet {
|
||||||
const data = sceneGraph.getData(scene);
|
const data = sceneGraph.getData(scene);
|
||||||
|
|
||||||
if (!(data instanceof SceneDataLayerSet)) {
|
if (!(data instanceof DashboardDataLayerSet)) {
|
||||||
throw new Error('SceneDataLayerSet not found');
|
throw new Error('DashboardDataLayerSet not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -552,6 +552,37 @@ exports[`Graph Migrations time regions should migrate 1`] = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Graph Migrations time regions should migrate in scenes dashboard 1`] = `
|
||||||
|
{
|
||||||
|
"alert": undefined,
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "gdev-testdata",
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {
|
||||||
|
"drawStyle": "points",
|
||||||
|
"spanNulls": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"overrides": [],
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true,
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Graph Migrations transforms should preserve "constant" transform 1`] = `
|
exports[`Graph Migrations transforms should preserve "constant" transform 1`] = `
|
||||||
{
|
{
|
||||||
"defaults": {
|
"defaults": {
|
||||||
|
@ -5,6 +5,9 @@ import { TooltipDisplayMode, SortOrder } from '@grafana/schema';
|
|||||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { DashboardModel, PanelModel as PanelModelState } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel as PanelModelState } from 'app/features/dashboard/state';
|
||||||
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
|
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
|
||||||
|
import { dataLayersToAnnotations } from 'app/features/dashboard-scene/serialization/dataLayersToAnnotations';
|
||||||
|
import { transformSaveModelToScene } from 'app/features/dashboard-scene/serialization/transformSaveModelToScene';
|
||||||
|
import { dashboardSceneGraph } from 'app/features/dashboard-scene/utils/dashboardSceneGraph';
|
||||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||||
|
|
||||||
import { graphPanelChangedHandler } from './migrations';
|
import { graphPanelChangedHandler } from './migrations';
|
||||||
@ -125,6 +128,42 @@ describe('Graph Migrations', () => {
|
|||||||
).toHaveLength(1);
|
).toHaveLength(1);
|
||||||
expect(panel).toMatchSnapshot();
|
expect(panel).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should migrate in scenes dashboard', () => {
|
||||||
|
const old = {
|
||||||
|
angular: {
|
||||||
|
timeRegions: [
|
||||||
|
{
|
||||||
|
colorMode: 'red',
|
||||||
|
fill: true,
|
||||||
|
fillColor: 'rgba(234, 112, 112, 0.12)',
|
||||||
|
fromDayOfWeek: 1,
|
||||||
|
line: true,
|
||||||
|
lineColor: 'rgba(237, 46, 24, 0.60)',
|
||||||
|
op: 'time',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const panel = { datasource: { type: 'datasource', uid: 'gdev-testdata' } } as PanelModel;
|
||||||
|
|
||||||
|
dashboard.panels.push(new PanelModelState(panel));
|
||||||
|
|
||||||
|
const scene = transformSaveModelToScene({ dashboard, meta: {} });
|
||||||
|
window.__grafanaSceneContext = scene;
|
||||||
|
|
||||||
|
panel.options = graphPanelChangedHandler(panel, 'graph', old, prevFieldConfig);
|
||||||
|
|
||||||
|
const layers = dashboardSceneGraph.getDataLayers(scene).state.annotationLayers;
|
||||||
|
const annotations = dataLayersToAnnotations(layers);
|
||||||
|
|
||||||
|
expect(annotations).toHaveLength(2); // built-in + time region
|
||||||
|
expect(
|
||||||
|
annotations.filter((annotation) => annotation.target?.queryType === GrafanaQueryType.TimeRegions)
|
||||||
|
).toHaveLength(1);
|
||||||
|
expect(panel).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('legend', () => {
|
describe('legend', () => {
|
||||||
|
@ -37,6 +37,9 @@ import {
|
|||||||
import { TimeRegionConfig } from 'app/core/utils/timeRegions';
|
import { TimeRegionConfig } from 'app/core/utils/timeRegions';
|
||||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
|
import { DashboardAnnotationsDataLayer } from 'app/features/dashboard-scene/scene/DashboardAnnotationsDataLayer';
|
||||||
|
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
||||||
|
import { dashboardSceneGraph } from 'app/features/dashboard-scene/utils/dashboardSceneGraph';
|
||||||
import { GrafanaQuery, GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
import { GrafanaQuery, GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||||
|
|
||||||
import { defaultGraphConfig } from './config';
|
import { defaultGraphConfig } from './config';
|
||||||
@ -61,17 +64,8 @@ export const graphPanelChangedHandler: PanelTypeChangedHandler = (
|
|||||||
panel: panel,
|
panel: panel,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dashboard = getDashboardSrv().getCurrent();
|
if (annotations?.length > 0) {
|
||||||
if (dashboard && annotations?.length > 0) {
|
addAnnotationsToDashboard(annotations);
|
||||||
dashboard.annotations.list = [...dashboard.annotations.list, ...annotations];
|
|
||||||
|
|
||||||
// Trigger a full dashboard refresh when annotations change
|
|
||||||
if (dashboardRefreshDebouncer == null) {
|
|
||||||
dashboardRefreshDebouncer = setTimeout(() => {
|
|
||||||
dashboardRefreshDebouncer = null;
|
|
||||||
getTimeSrv().refreshTimeModel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panel.fieldConfig = fieldConfig; // Mutates the incoming panel
|
panel.fieldConfig = fieldConfig; // Mutates the incoming panel
|
||||||
@ -400,7 +394,7 @@ export function graphToTimeseriesOptions(angular: any): {
|
|||||||
// timeRegions migration
|
// timeRegions migration
|
||||||
if (angular.timeRegions?.length) {
|
if (angular.timeRegions?.length) {
|
||||||
let regions = angular.timeRegions.map((old: GraphTimeRegionConfig, idx: number) => ({
|
let regions = angular.timeRegions.map((old: GraphTimeRegionConfig, idx: number) => ({
|
||||||
name: `T${idx + 1}`,
|
name: `T${idx}`,
|
||||||
color: old.colorMode !== 'custom' ? old.colorMode : old.fillColor,
|
color: old.colorMode !== 'custom' ? old.colorMode : old.fillColor,
|
||||||
line: old.line,
|
line: old.line,
|
||||||
fill: old.fill,
|
fill: old.fill,
|
||||||
@ -423,7 +417,7 @@ export function graphToTimeseriesOptions(angular: any): {
|
|||||||
ids: [angular.panel.id],
|
ids: [angular.panel.id],
|
||||||
},
|
},
|
||||||
iconColor: region.fillColor ?? (region as any).color,
|
iconColor: region.fillColor ?? (region as any).color,
|
||||||
name: `T${idx + 1}`,
|
name: `Time region for panel ${angular.panel.title}${idx > 0 ? ` ${idx}` : ''}`,
|
||||||
target: {
|
target: {
|
||||||
queryType: GrafanaQueryType.TimeRegions,
|
queryType: GrafanaQueryType.TimeRegions,
|
||||||
refId: 'Anno',
|
refId: 'Anno',
|
||||||
@ -751,3 +745,40 @@ function getStackingFromOverrides(value: Boolean | string) {
|
|||||||
group: isString(value) ? value : defaultGroupName,
|
group: isString(value) ? value : defaultGroupName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addAnnotationsToDashboard(annotations: AnnotationQuery[]) {
|
||||||
|
const scene = window.__grafanaSceneContext;
|
||||||
|
|
||||||
|
if (scene instanceof DashboardScene) {
|
||||||
|
const dataLayers = dashboardSceneGraph.getDataLayers(scene);
|
||||||
|
const annotationLayers = [...dataLayers.state.annotationLayers];
|
||||||
|
|
||||||
|
for (let annotation of annotations) {
|
||||||
|
const newAnnotation = new DashboardAnnotationsDataLayer({
|
||||||
|
key: `annotations-${annotation.name}`,
|
||||||
|
query: annotation,
|
||||||
|
name: annotation.name,
|
||||||
|
isEnabled: annotation.enable,
|
||||||
|
isHidden: annotation.hide,
|
||||||
|
});
|
||||||
|
|
||||||
|
annotationLayers.push(newAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLayers.setState({ annotationLayers });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dashboard = getDashboardSrv().getCurrent();
|
||||||
|
if (dashboard) {
|
||||||
|
dashboard.annotations.list = [...dashboard.annotations.list, ...annotations];
|
||||||
|
|
||||||
|
// Trigger a full dashboard refresh when annotations change
|
||||||
|
if (dashboardRefreshDebouncer == null) {
|
||||||
|
dashboardRefreshDebouncer = setTimeout(() => {
|
||||||
|
dashboardRefreshDebouncer = null;
|
||||||
|
getTimeSrv().refreshTimeModel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user