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 { AdHocFiltersVariable, dataLayers, SceneDataLayerSet, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes';
|
||||
import { AdHocFiltersVariable, dataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
||||
import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api';
|
||||
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
@ -128,16 +129,16 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte
|
||||
}
|
||||
|
||||
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
|
||||
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.state.isEnabled && layer.state.query.builtIn) {
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
GroupByVariable,
|
||||
QueryVariable,
|
||||
SceneDataLayerControls,
|
||||
SceneDataLayerSet,
|
||||
SceneDataTransformer,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
@ -42,6 +41,7 @@ import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard
|
||||
import { DashboardDataDTO } from 'app/types';
|
||||
|
||||
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
@ -1230,26 +1230,26 @@ describe('transformSaveModelToScene', () => {
|
||||
it('Should build correct scene model', () => {
|
||||
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);
|
||||
|
||||
const dataLayers = scene.state.$data as SceneDataLayerSet;
|
||||
expect(dataLayers.state.layers).toHaveLength(4);
|
||||
expect(dataLayers.state.layers[0].state.name).toBe('Annotations & Alerts');
|
||||
expect(dataLayers.state.layers[0].state.isEnabled).toBe(true);
|
||||
expect(dataLayers.state.layers[0].state.isHidden).toBe(false);
|
||||
const dataLayers = scene.state.$data as DashboardDataLayerSet;
|
||||
expect(dataLayers.state.annotationLayers).toHaveLength(4);
|
||||
expect(dataLayers.state.annotationLayers[0].state.name).toBe('Annotations & Alerts');
|
||||
expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(true);
|
||||
expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(false);
|
||||
|
||||
expect(dataLayers.state.layers[1].state.name).toBe('Enabled');
|
||||
expect(dataLayers.state.layers[1].state.isEnabled).toBe(true);
|
||||
expect(dataLayers.state.layers[1].state.isHidden).toBe(false);
|
||||
expect(dataLayers.state.annotationLayers[1].state.name).toBe('Enabled');
|
||||
expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(true);
|
||||
expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(false);
|
||||
|
||||
expect(dataLayers.state.layers[2].state.name).toBe('Disabled');
|
||||
expect(dataLayers.state.layers[2].state.isEnabled).toBe(false);
|
||||
expect(dataLayers.state.layers[2].state.isHidden).toBe(false);
|
||||
expect(dataLayers.state.annotationLayers[2].state.name).toBe('Disabled');
|
||||
expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(false);
|
||||
expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(false);
|
||||
|
||||
expect(dataLayers.state.layers[3].state.name).toBe('Hidden');
|
||||
expect(dataLayers.state.layers[3].state.isEnabled).toBe(true);
|
||||
expect(dataLayers.state.layers[3].state.isHidden).toBe(true);
|
||||
expect(dataLayers.state.annotationLayers[3].state.name).toBe('Hidden');
|
||||
expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(true);
|
||||
expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1258,12 +1258,11 @@ describe('transformSaveModelToScene', () => {
|
||||
config.unifiedAlertingEnabled = true;
|
||||
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);
|
||||
|
||||
const dataLayers = scene.state.$data as SceneDataLayerSet;
|
||||
expect(dataLayers.state.layers).toHaveLength(5);
|
||||
expect(dataLayers.state.layers[4].state.name).toBe('Alert States');
|
||||
const dataLayers = scene.state.$data as DashboardDataLayerSet;
|
||||
expect(dataLayers.state.alertStatesLayer).toBeDefined();
|
||||
});
|
||||
|
||||
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 = {};
|
||||
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);
|
||||
|
||||
const dataLayers = scene.state.$data as SceneDataLayerSet;
|
||||
expect(dataLayers.state.layers).toHaveLength(5);
|
||||
expect(dataLayers.state.layers[4].state.name).toBe('Alert States');
|
||||
const dataLayers = scene.state.$data as DashboardDataLayerSet;
|
||||
expect(dataLayers.state.alertStatesLayer).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
behaviors,
|
||||
VizPanelState,
|
||||
SceneGridItemLike,
|
||||
SceneDataLayerSet,
|
||||
SceneDataLayerProvider,
|
||||
SceneDataLayerControls,
|
||||
TextBoxVariable,
|
||||
@ -37,6 +36,7 @@ import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||
import { DashboardControls } from '../scene/DashboardControls';
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
|
||||
import { registerDashboardMacro } from '../scene/DashboardMacro';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
@ -216,8 +216,9 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]):
|
||||
}
|
||||
|
||||
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
|
||||
let variables: SceneVariableSet | undefined = undefined;
|
||||
let layers: SceneDataLayerProvider[] = [];
|
||||
let variables: SceneVariableSet | undefined;
|
||||
let annotationLayers: SceneDataLayerProvider[] = [];
|
||||
let alertStatesLayer: AlertStatesDataLayer | undefined;
|
||||
|
||||
if (oldModel.templating?.list?.length) {
|
||||
const variableObjects = oldModel.templating.list
|
||||
@ -244,7 +245,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
||||
}
|
||||
|
||||
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
|
||||
return new DashboardAnnotationsDataLayer({
|
||||
key: `annotations-${a.name}`,
|
||||
@ -264,12 +265,10 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
||||
}
|
||||
|
||||
if (shouldUseAlertStatesLayer) {
|
||||
layers.push(
|
||||
new AlertStatesDataLayer({
|
||||
alertStatesLayer = new AlertStatesDataLayer({
|
||||
key: 'alert-states',
|
||||
name: 'Alert States',
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const dashboardScene = new DashboardScene({
|
||||
@ -307,12 +306,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
||||
trackIfIsEmpty,
|
||||
new behaviors.LiveNowTimer(oldModel.liveNow),
|
||||
],
|
||||
$data:
|
||||
layers.length > 0
|
||||
? new SceneDataLayerSet({
|
||||
layers,
|
||||
})
|
||||
: undefined,
|
||||
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
|
||||
controls: new DashboardControls({
|
||||
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
|
||||
timePicker: new SceneTimePicker({}),
|
||||
|
@ -16,13 +16,14 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
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 { PanelModel } from 'app/features/dashboard/state';
|
||||
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
|
||||
import { reduceTransformRegistryItem } from 'app/features/transformers/editors/ReduceTransformerEditor';
|
||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
||||
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||
@ -402,10 +403,11 @@ describe('transformSceneToSaveModel', () => {
|
||||
expect(saveModel.annotations?.list?.length).toBe(4);
|
||||
expect(saveModel.annotations?.list).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should transform annotations to save model after state changes', () => {
|
||||
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 hiddenLayer = layers[3];
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
SceneDataTransformer,
|
||||
SceneVariableSet,
|
||||
LocalValueVariable,
|
||||
SceneDataLayerSet,
|
||||
} from '@grafana/scenes';
|
||||
import {
|
||||
AnnotationQuery,
|
||||
@ -33,6 +32,7 @@ import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/Dashboard
|
||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||
|
||||
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
@ -77,10 +77,9 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { map, of } from 'rxjs';
|
||||
|
||||
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 { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { activateFullSceneTree } from '../utils/test-utils';
|
||||
@ -48,40 +48,26 @@ jest.mock('@grafana/runtime', () => ({
|
||||
describe('AnnotationsEditView', () => {
|
||||
describe('Dashboard annotations state', () => {
|
||||
let annotationsView: AnnotationsEditView;
|
||||
let dashboardScene: DashboardScene;
|
||||
|
||||
beforeEach(async () => {
|
||||
const result = await buildTestScene();
|
||||
annotationsView = result.annotationsView;
|
||||
dashboardScene = result.dashboard;
|
||||
});
|
||||
|
||||
it('should return the correct urlKey', () => {
|
||||
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', () => {
|
||||
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||
|
||||
annotationsView.onNew();
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(3);
|
||||
expect(dataLayers?.state.layers[1].state.name).toBe(newAnnotationName);
|
||||
expect(dataLayers?.state.layers[1].isActive).toBe(true);
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers[1].state.name).toBe(newAnnotationName);
|
||||
expect(dataLayers?.state.annotationLayers[1].isActive).toBe(true);
|
||||
});
|
||||
|
||||
it('should move an annotation up one position', () => {
|
||||
@ -89,13 +75,13 @@ describe('AnnotationsEditView', () => {
|
||||
|
||||
annotationsView.onNew();
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(3);
|
||||
expect(dataLayers?.state.layers[0].state.name).toBe('test');
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('test');
|
||||
|
||||
annotationsView.onMove(1, MoveDirection.UP);
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(3);
|
||||
expect(dataLayers?.state.layers[0].state.name).toBe(newAnnotationName);
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers[0].state.name).toBe(newAnnotationName);
|
||||
});
|
||||
|
||||
it('should move an annotation down one position', () => {
|
||||
@ -103,33 +89,32 @@ describe('AnnotationsEditView', () => {
|
||||
|
||||
annotationsView.onNew();
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(3);
|
||||
expect(dataLayers?.state.layers[0].state.name).toBe('test');
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('test');
|
||||
|
||||
annotationsView.onMove(0, MoveDirection.DOWN);
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(3);
|
||||
expect(dataLayers?.state.layers[0].state.name).toBe(newAnnotationName);
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers[0].state.name).toBe(newAnnotationName);
|
||||
});
|
||||
|
||||
it('should delete annotation at index', () => {
|
||||
const dataLayers = dashboardSceneGraph.getDataLayers(annotationsView.getDashboard());
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(2);
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||
|
||||
annotationsView.onDelete(0);
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(1);
|
||||
expect(dataLayers?.state.layers[0].state.name).toBe('Alert States');
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should update an annotation at index', () => {
|
||||
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 = {
|
||||
...(dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query,
|
||||
...(dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query,
|
||||
};
|
||||
|
||||
annotation.name = 'new name';
|
||||
@ -138,12 +123,16 @@ describe('AnnotationsEditView', () => {
|
||||
annotation.iconColor = 'blue';
|
||||
annotationsView.onUpdate(annotation, 0);
|
||||
|
||||
expect(dataLayers?.state.layers.length).toBe(2);
|
||||
expect(dataLayers?.state.layers[0].state.name).toBe('new name');
|
||||
expect((dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query.name).toBe('new name');
|
||||
expect((dataLayers?.state.layers[0] as dataLayers.AnnotationsDataLayer).state.query.hide).toBe(true);
|
||||
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.length).toBe(1);
|
||||
expect(dataLayers?.state.annotationLayers[0].state.name).toBe('new name');
|
||||
expect((dataLayers?.state.annotationLayers[0] as dataLayers.AnnotationsDataLayer).state.query.name).toBe(
|
||||
'new name'
|
||||
);
|
||||
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: {
|
||||
canEdit: true,
|
||||
},
|
||||
$data: new SceneDataLayerSet({
|
||||
layers: [
|
||||
$data: new DashboardDataLayerSet({
|
||||
annotationLayers: [
|
||||
new DashboardAnnotationsDataLayer({
|
||||
key: `annotations-test`,
|
||||
query: {
|
||||
@ -175,10 +164,6 @@ async function buildTestScene() {
|
||||
isEnabled: true,
|
||||
isHidden: false,
|
||||
}),
|
||||
new AlertStatesDataLayer({
|
||||
key: 'alert-states',
|
||||
name: 'Alert States',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
body: new SceneGridLayout({
|
||||
|
@ -40,7 +40,7 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
||||
|
||||
public getDataLayer(editIndex: number): dataLayers.AnnotationsDataLayer {
|
||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||
const layer = data.state.layers[editIndex];
|
||||
const layer = data.state.annotationLayers[editIndex];
|
||||
|
||||
if (!(layer instanceof dataLayers.AnnotationsDataLayer)) {
|
||||
throw new Error('AnnotationsDataLayer not found at index ' + editIndex);
|
||||
@ -49,12 +49,6 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
||||
return layer;
|
||||
}
|
||||
|
||||
public getAnnotationsLength(): number {
|
||||
return dashboardSceneGraph
|
||||
.getDataLayers(this._dashboard)
|
||||
.state.layers.filter((layer) => layer instanceof DashboardAnnotationsDataLayer).length;
|
||||
}
|
||||
|
||||
public getDashboard(): DashboardScene {
|
||||
return this._dashboard;
|
||||
}
|
||||
@ -76,18 +70,9 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
||||
|
||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||
|
||||
const layers = [...data.state.layers];
|
||||
data.addAnnotationLayer(newAnnotation);
|
||||
|
||||
//keep annotation layers together
|
||||
layers.splice(this.getAnnotationsLength(), 0, newAnnotation);
|
||||
|
||||
data.setState({
|
||||
layers,
|
||||
});
|
||||
|
||||
newAnnotation.activate();
|
||||
|
||||
this.setState({ editIndex: this.getAnnotationsLength() - 1 });
|
||||
this.setState({ editIndex: data.state.annotationLayers.length - 1 });
|
||||
};
|
||||
|
||||
public onEdit = (idx: number) => {
|
||||
@ -100,25 +85,21 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
||||
|
||||
public onMove = (idx: number, direction: MoveDirection) => {
|
||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||
const layers = [...data.state.annotationLayers];
|
||||
|
||||
const layers = [...data.state.layers];
|
||||
const [layer] = layers.splice(idx, 1);
|
||||
layers.splice(idx + direction, 0, layer);
|
||||
|
||||
data.setState({
|
||||
layers,
|
||||
});
|
||||
data.setState({ annotationLayers: layers });
|
||||
};
|
||||
|
||||
public onDelete = (idx: number) => {
|
||||
const data = dashboardSceneGraph.getDataLayers(this._dashboard);
|
||||
const layers = [...data.state.annotationLayers];
|
||||
|
||||
const layers = [...data.state.layers];
|
||||
layers.splice(idx, 1);
|
||||
|
||||
data.setState({
|
||||
layers,
|
||||
});
|
||||
data.setState({ annotationLayers: layers });
|
||||
};
|
||||
|
||||
public onUpdate = (annotation: AnnotationQuery, editIndex: number) => {
|
||||
@ -139,14 +120,14 @@ export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewStat
|
||||
|
||||
function AnnotationsSettingsView({ model }: SceneComponentProps<AnnotationsEditView>) {
|
||||
const dashboard = model.getDashboard();
|
||||
const { layers } = dashboardSceneGraph.getDataLayers(dashboard).useState();
|
||||
const { annotationLayers } = dashboardSceneGraph.getDataLayers(dashboard).useState();
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const { editIndex } = model.useState();
|
||||
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 (
|
||||
<AnnotationsSettingsEditView
|
||||
annotationLayer={model.getDataLayer(editIndex)}
|
||||
|
@ -134,6 +134,6 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD
|
||||
const getStyles = () => ({
|
||||
table: css({
|
||||
width: '100%',
|
||||
overflowX: 'scroll',
|
||||
overflowX: 'auto',
|
||||
}),
|
||||
});
|
||||
|
@ -1,17 +1,9 @@
|
||||
import {
|
||||
SceneDataLayerSet,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneQueryRunner,
|
||||
SceneTimeRange,
|
||||
VizPanel,
|
||||
behaviors,
|
||||
} from '@grafana/scenes';
|
||||
import { SceneGridLayout, SceneGridRow, SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes';
|
||||
import { DashboardCursorSync } from '@grafana/schema';
|
||||
|
||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||
import { DashboardControls } from '../scene/DashboardControls';
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardGridItem } from '../scene/DashboardGridItem';
|
||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
@ -75,8 +67,8 @@ describe('dashboardSceneGraph', () => {
|
||||
it('should return the scene data layers', () => {
|
||||
const dataLayers = dashboardSceneGraph.getDataLayers(scene);
|
||||
|
||||
expect(dataLayers).toBeInstanceOf(SceneDataLayerSet);
|
||||
expect(dataLayers?.state.layers.length).toBe(2);
|
||||
expect(dataLayers).toBeInstanceOf(DashboardDataLayerSet);
|
||||
expect(dataLayers?.state.annotationLayers.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should throw if there are no scene data layers', () => {
|
||||
@ -84,7 +76,7 @@ describe('dashboardSceneGraph', () => {
|
||||
$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,
|
||||
}),
|
||||
],
|
||||
$data: new SceneDataLayerSet({
|
||||
layers: [
|
||||
$data: new DashboardDataLayerSet({
|
||||
annotationLayers: [
|
||||
new DashboardAnnotationsDataLayer({
|
||||
key: `annotation`,
|
||||
query: {
|
||||
@ -255,10 +247,6 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
||||
isEnabled: true,
|
||||
isHidden: false,
|
||||
}),
|
||||
new AlertStatesDataLayer({
|
||||
key: 'alert-states',
|
||||
name: 'Alert States',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
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 { DashboardScene } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
@ -53,11 +54,11 @@ function getVizPanels(scene: DashboardScene): VizPanel[] {
|
||||
return panels;
|
||||
}
|
||||
|
||||
function getDataLayers(scene: DashboardScene): SceneDataLayerSet {
|
||||
function getDataLayers(scene: DashboardScene): DashboardDataLayerSet {
|
||||
const data = sceneGraph.getData(scene);
|
||||
|
||||
if (!(data instanceof SceneDataLayerSet)) {
|
||||
throw new Error('SceneDataLayerSet not found');
|
||||
if (!(data instanceof DashboardDataLayerSet)) {
|
||||
throw new Error('DashboardDataLayerSet not found');
|
||||
}
|
||||
|
||||
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`] = `
|
||||
{
|
||||
"defaults": {
|
||||
|
@ -5,6 +5,9 @@ import { TooltipDisplayMode, SortOrder } from '@grafana/schema';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { DashboardModel, PanelModel as PanelModelState } from 'app/features/dashboard/state';
|
||||
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 { graphPanelChangedHandler } from './migrations';
|
||||
@ -125,6 +128,42 @@ describe('Graph Migrations', () => {
|
||||
).toHaveLength(1);
|
||||
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', () => {
|
||||
|
@ -37,6 +37,9 @@ import {
|
||||
import { TimeRegionConfig } from 'app/core/utils/timeRegions';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
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 { defaultGraphConfig } from './config';
|
||||
@ -61,17 +64,8 @@ export const graphPanelChangedHandler: PanelTypeChangedHandler = (
|
||||
panel: panel,
|
||||
});
|
||||
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
if (dashboard && annotations?.length > 0) {
|
||||
dashboard.annotations.list = [...dashboard.annotations.list, ...annotations];
|
||||
|
||||
// Trigger a full dashboard refresh when annotations change
|
||||
if (dashboardRefreshDebouncer == null) {
|
||||
dashboardRefreshDebouncer = setTimeout(() => {
|
||||
dashboardRefreshDebouncer = null;
|
||||
getTimeSrv().refreshTimeModel();
|
||||
});
|
||||
}
|
||||
if (annotations?.length > 0) {
|
||||
addAnnotationsToDashboard(annotations);
|
||||
}
|
||||
|
||||
panel.fieldConfig = fieldConfig; // Mutates the incoming panel
|
||||
@ -400,7 +394,7 @@ export function graphToTimeseriesOptions(angular: any): {
|
||||
// timeRegions migration
|
||||
if (angular.timeRegions?.length) {
|
||||
let regions = angular.timeRegions.map((old: GraphTimeRegionConfig, idx: number) => ({
|
||||
name: `T${idx + 1}`,
|
||||
name: `T${idx}`,
|
||||
color: old.colorMode !== 'custom' ? old.colorMode : old.fillColor,
|
||||
line: old.line,
|
||||
fill: old.fill,
|
||||
@ -423,7 +417,7 @@ export function graphToTimeseriesOptions(angular: any): {
|
||||
ids: [angular.panel.id],
|
||||
},
|
||||
iconColor: region.fillColor ?? (region as any).color,
|
||||
name: `T${idx + 1}`,
|
||||
name: `Time region for panel ${angular.panel.title}${idx > 0 ? ` ${idx}` : ''}`,
|
||||
target: {
|
||||
queryType: GrafanaQueryType.TimeRegions,
|
||||
refId: 'Anno',
|
||||
@ -751,3 +745,40 @@ function getStackingFromOverrides(value: Boolean | string) {
|
||||
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