mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SchemaV2: Rows in dashboard schema v2 (#99239)
* Testing out rows in schemav2 * update schema * loading sort of works * descibe position in relation to row * add row repeats by variable * explain ts-expect-error * Save repeats as well * Update tests for repeat behavior of rows * Don't add the clones of the repeated rows * Add row support for response transformer for V2 * Add row actions * fix panel name * fix merge issue * fix tests * Implement ensureV1 * set key of GridRow * fix lint issue * When going from V2 to V1 rows should be assigned unique ids following max panel id * remove old comment * Add panel repeats in V2 -> V1 transform
This commit is contained in:
parent
1e3783cc11
commit
800c9fa3e6
@ -483,6 +483,11 @@ RepeatOptions: {
|
||||
maxPerRow?: int
|
||||
}
|
||||
|
||||
RowRepeatOptions: {
|
||||
mode: RepeatMode,
|
||||
value: string
|
||||
}
|
||||
|
||||
GridLayoutItemSpec: {
|
||||
x: int
|
||||
y: int
|
||||
@ -497,8 +502,21 @@ GridLayoutItemKind: {
|
||||
spec: GridLayoutItemSpec
|
||||
}
|
||||
|
||||
GridLayoutRowKind: {
|
||||
kind: "GridLayoutRow"
|
||||
spec: GridLayoutRowSpec
|
||||
}
|
||||
|
||||
GridLayoutRowSpec: {
|
||||
y: int
|
||||
collapsed: bool
|
||||
title: string
|
||||
elements: [...GridLayoutItemKind] // Grid items in the row will have their Y value be relative to the rows Y value. This means a panel positioned at Y: 0 in a row with Y: 10 will be positioned at Y: 11 (row header has a heigh of 1) in the dashboard.
|
||||
repeat?: RowRepeatOptions
|
||||
}
|
||||
|
||||
GridLayoutSpec: {
|
||||
items: [...GridLayoutItemKind]
|
||||
items: [...GridLayoutItemKind | GridLayoutRowKind]
|
||||
}
|
||||
|
||||
GridLayoutKind: {
|
||||
|
@ -191,6 +191,60 @@ export const handyTestingSchema: DashboardV2Spec = {
|
||||
},
|
||||
},
|
||||
},
|
||||
'panel-3': {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [
|
||||
{
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
refId: 'A',
|
||||
datasource: {
|
||||
type: 'prometheus',
|
||||
uid: 'datasource1',
|
||||
},
|
||||
query: {
|
||||
kind: 'prometheus',
|
||||
spec: {
|
||||
expr: 'test-query',
|
||||
},
|
||||
},
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
queryOptions: {
|
||||
timeFrom: '1h',
|
||||
maxDataPoints: 100,
|
||||
timeShift: '1h',
|
||||
queryCachingTTL: 60,
|
||||
interval: '1m',
|
||||
cacheTimeout: '1m',
|
||||
hideTimeOverride: false,
|
||||
},
|
||||
transformations: [],
|
||||
},
|
||||
},
|
||||
description: 'Test Description',
|
||||
links: [],
|
||||
title: 'Test Panel 3',
|
||||
id: 3,
|
||||
vizConfig: {
|
||||
kind: 'timeseries',
|
||||
spec: {
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
options: {},
|
||||
pluginVersion: '7.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
kind: 'GridLayout',
|
||||
@ -203,8 +257,8 @@ export const handyTestingSchema: DashboardV2Spec = {
|
||||
kind: 'ElementReference',
|
||||
name: 'panel-1',
|
||||
},
|
||||
height: 100,
|
||||
width: 200,
|
||||
height: 10,
|
||||
width: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
repeat: {
|
||||
@ -221,18 +275,42 @@ export const handyTestingSchema: DashboardV2Spec = {
|
||||
kind: 'ElementReference',
|
||||
name: 'panel-2',
|
||||
},
|
||||
height: 100,
|
||||
height: 10,
|
||||
width: 200,
|
||||
x: 0,
|
||||
y: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'GridLayoutRow',
|
||||
spec: {
|
||||
y: 20,
|
||||
collapsed: false,
|
||||
title: 'Row 1',
|
||||
repeat: { value: 'customVar', mode: 'variable' },
|
||||
elements: [
|
||||
{
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: 'panel-3',
|
||||
},
|
||||
height: 10,
|
||||
width: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
links: [
|
||||
{
|
||||
asDropdown: false,
|
||||
asDropdown: true,
|
||||
icon: '',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
|
@ -700,6 +700,16 @@ export const defaultRepeatOptions = (): RepeatOptions => ({
|
||||
value: "",
|
||||
});
|
||||
|
||||
export interface RowRepeatOptions {
|
||||
mode: "variable";
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const defaultRowRepeatOptions = (): RowRepeatOptions => ({
|
||||
mode: RepeatMode,
|
||||
value: "",
|
||||
});
|
||||
|
||||
export interface GridLayoutItemSpec {
|
||||
x: number;
|
||||
y: number;
|
||||
@ -728,8 +738,33 @@ export const defaultGridLayoutItemKind = (): GridLayoutItemKind => ({
|
||||
spec: defaultGridLayoutItemSpec(),
|
||||
});
|
||||
|
||||
export interface GridLayoutRowKind {
|
||||
kind: "GridLayoutRow";
|
||||
spec: GridLayoutRowSpec;
|
||||
}
|
||||
|
||||
export const defaultGridLayoutRowKind = (): GridLayoutRowKind => ({
|
||||
kind: "GridLayoutRow",
|
||||
spec: defaultGridLayoutRowSpec(),
|
||||
});
|
||||
|
||||
export interface GridLayoutRowSpec {
|
||||
y: number;
|
||||
collapsed: boolean;
|
||||
title: string;
|
||||
elements: GridLayoutItemKind[];
|
||||
repeat?: RowRepeatOptions;
|
||||
}
|
||||
|
||||
export const defaultGridLayoutRowSpec = (): GridLayoutRowSpec => ({
|
||||
y: 0,
|
||||
collapsed: false,
|
||||
title: "",
|
||||
elements: [],
|
||||
});
|
||||
|
||||
export interface GridLayoutSpec {
|
||||
items: GridLayoutItemKind[];
|
||||
items: (GridLayoutItemKind | GridLayoutRowKind)[];
|
||||
}
|
||||
|
||||
export const defaultGridLayoutSpec = (): GridLayoutSpec => ({
|
||||
|
@ -88,6 +88,34 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
|
||||
},
|
||||
},
|
||||
},
|
||||
"panel-2": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [],
|
||||
"queryOptions": {},
|
||||
"transformations": [],
|
||||
},
|
||||
},
|
||||
"description": "Test Description 2",
|
||||
"id": 2,
|
||||
"links": [],
|
||||
"title": "Test Panel 2",
|
||||
"vizConfig": {
|
||||
"kind": "graph",
|
||||
"spec": {
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [],
|
||||
},
|
||||
"options": {},
|
||||
"pluginVersion": "7.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"layout": {
|
||||
"kind": "GridLayout",
|
||||
@ -100,12 +128,39 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-1",
|
||||
},
|
||||
"height": 0,
|
||||
"height": 10,
|
||||
"width": 0,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutRow",
|
||||
"spec": {
|
||||
"collapsed": false,
|
||||
"elements": [
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-2",
|
||||
},
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
"repeat": {
|
||||
"mode": "variable",
|
||||
"value": "customVar",
|
||||
},
|
||||
"title": "Test Row",
|
||||
"y": 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -2,3 +2,5 @@ export const GRAFANA_DATASOURCE_REF = {
|
||||
name: 'grafana',
|
||||
uid: 'grafana',
|
||||
};
|
||||
|
||||
export const GRID_ROW_HEIGHT = 1;
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
GroupByVariable,
|
||||
AdHocFiltersVariable,
|
||||
SceneDataTransformer,
|
||||
SceneGridRow,
|
||||
} from '@grafana/scenes';
|
||||
import {
|
||||
AdhocVariableKind,
|
||||
@ -20,6 +21,7 @@ import {
|
||||
CustomVariableKind,
|
||||
DashboardV2Spec,
|
||||
DatasourceVariableKind,
|
||||
GridLayoutItemSpec,
|
||||
GroupByVariableKind,
|
||||
IntervalVariableKind,
|
||||
QueryVariableKind,
|
||||
@ -44,7 +46,7 @@ import {
|
||||
} from './transformSaveModelSchemaV2ToScene';
|
||||
import { transformCursorSynctoEnum } from './transformToV2TypesUtils';
|
||||
|
||||
const defaultDashboard: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
export const defaultDashboard: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
metadata: {
|
||||
name: 'dashboard-uid',
|
||||
@ -220,16 +222,16 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
|
||||
// VizPanel
|
||||
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
|
||||
expect(vizPanels).toHaveLength(2);
|
||||
expect(vizPanels).toHaveLength(3);
|
||||
|
||||
// Layout
|
||||
const layout = scene.state.body as DefaultGridLayoutManager;
|
||||
|
||||
// Panel
|
||||
const panel = getPanelElement(dash, 'panel-1')!;
|
||||
expect(layout.state.grid.state.children.length).toBe(2);
|
||||
expect(layout.state.grid.state.children.length).toBe(3);
|
||||
expect(layout.state.grid.state.children[0].state.key).toBe(`grid-item-${panel.spec.id}`);
|
||||
const gridLayoutItemSpec = dash.layout.spec.items[0].spec;
|
||||
const gridLayoutItemSpec = dash.layout.spec.items[0].spec as GridLayoutItemSpec;
|
||||
expect(layout.state.grid.state.children[0].state.width).toBe(gridLayoutItemSpec.width);
|
||||
expect(layout.state.grid.state.children[0].state.height).toBe(gridLayoutItemSpec.height);
|
||||
expect(layout.state.grid.state.children[0].state.x).toBe(gridLayoutItemSpec.x);
|
||||
@ -240,7 +242,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
// Library Panel
|
||||
const libraryPanel = getLibraryPanelElement(dash, 'panel-2')!;
|
||||
expect(layout.state.grid.state.children[1].state.key).toBe(`grid-item-${libraryPanel.spec.id}`);
|
||||
const libraryGridLayoutItemSpec = dash.layout.spec.items[1].spec;
|
||||
const libraryGridLayoutItemSpec = dash.layout.spec.items[1].spec as GridLayoutItemSpec;
|
||||
expect(layout.state.grid.state.children[1].state.width).toBe(libraryGridLayoutItemSpec.width);
|
||||
expect(layout.state.grid.state.children[1].state.height).toBe(libraryGridLayoutItemSpec.height);
|
||||
expect(layout.state.grid.state.children[1].state.x).toBe(libraryGridLayoutItemSpec.x);
|
||||
@ -248,6 +250,9 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
const vizLibraryPanel = vizPanels.find((p) => p.state.key === 'panel-2')!;
|
||||
validateVizPanel(vizLibraryPanel, dash);
|
||||
|
||||
expect((layout.state.grid.state.children[2] as SceneGridRow).state.isCollapsed).toBe(false);
|
||||
expect((layout.state.grid.state.children[2] as SceneGridRow).state.y).toBe(20);
|
||||
|
||||
// Transformations
|
||||
const panelWithTransformations = vizPanels.find((p) => p.state.key === 'panel-1')!;
|
||||
expect((panelWithTransformations.state.$data as SceneDataTransformer)?.state.transformations[0]).toEqual(
|
||||
@ -278,7 +283,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
const scene = transformSaveModelSchemaV2ToScene(dashboard);
|
||||
|
||||
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
|
||||
expect(vizPanels.length).toBe(2);
|
||||
expect(vizPanels.length).toBe(3);
|
||||
expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.type).toBe('mixed');
|
||||
expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.uid).toBe(MIXED_DATASOURCE_NAME);
|
||||
});
|
||||
@ -306,7 +311,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
const scene = transformSaveModelSchemaV2ToScene(dashboard);
|
||||
|
||||
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
|
||||
expect(vizPanels.length).toBe(2);
|
||||
expect(vizPanels.length).toBe(3);
|
||||
expect(getQueryRunnerFor(vizPanels[0])?.state.datasource).toBeUndefined();
|
||||
});
|
||||
|
||||
@ -330,7 +335,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
|
||||
const scene = transformSaveModelSchemaV2ToScene(dashboard);
|
||||
|
||||
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
|
||||
expect(vizPanels.length).toBe(2);
|
||||
expect(vizPanels.length).toBe(3);
|
||||
expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.type).toBe('mixed');
|
||||
expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.uid).toBe(MIXED_DATASOURCE_NAME);
|
||||
});
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
SceneDataTransformer,
|
||||
SceneGridItemLike,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneObject,
|
||||
SceneQueryRunner,
|
||||
SceneRefreshPicker,
|
||||
@ -44,6 +45,7 @@ import {
|
||||
defaultIntervalVariableKind,
|
||||
defaultQueryVariableKind,
|
||||
defaultTextVariableKind,
|
||||
GridLayoutItemSpec,
|
||||
GroupByVariableKind,
|
||||
IntervalVariableKind,
|
||||
LibraryPanelKind,
|
||||
@ -79,13 +81,16 @@ import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||
import { panelLinksBehavior, panelMenuBehavior } from '../scene/PanelMenuBehavior';
|
||||
import { PanelNotices } from '../scene/PanelNotices';
|
||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||
import { AngularDeprecation } from '../scene/angular/AngularDeprecation';
|
||||
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
||||
import { RowActions } from '../scene/row-actions/RowActions';
|
||||
import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
|
||||
import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState';
|
||||
import { getDashboardSceneFor, getIntervalsFromQueryString, getVizPanelKeyForPanelId } from '../utils/utils';
|
||||
|
||||
import { GRID_ROW_HEIGHT } from './const';
|
||||
import { SnapshotVariable } from './custom-variables/SnapshotVariable';
|
||||
import { registerPanelInteractionsReporter } from './transformSaveModelToScene';
|
||||
import {
|
||||
@ -228,6 +233,22 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
return dashboardScene;
|
||||
}
|
||||
|
||||
function buildGridItem(gridItem: GridLayoutItemSpec, panel: PanelKind, yOverride?: number): DashboardGridItem {
|
||||
const vizPanel = buildVizPanel(panel);
|
||||
return new DashboardGridItem({
|
||||
key: `grid-item-${panel.spec.id}`,
|
||||
x: gridItem.x,
|
||||
y: yOverride ?? gridItem.y,
|
||||
width: gridItem.repeat?.direction === 'h' ? 24 : gridItem.width,
|
||||
height: gridItem.height,
|
||||
itemHeight: gridItem.height,
|
||||
body: vizPanel,
|
||||
variableName: gridItem.repeat?.value,
|
||||
repeatDirection: gridItem.repeat?.direction,
|
||||
maxPerRow: gridItem.repeat?.maxPerRow,
|
||||
});
|
||||
}
|
||||
|
||||
function createSceneGridLayoutForItems(dashboard: DashboardV2Spec): SceneGridItemLike[] {
|
||||
const gridElements = dashboard.layout.spec.items;
|
||||
|
||||
@ -240,20 +261,7 @@ function createSceneGridLayoutForItems(dashboard: DashboardV2Spec): SceneGridIte
|
||||
}
|
||||
|
||||
if (panel.kind === 'Panel') {
|
||||
const vizPanel = buildVizPanel(panel);
|
||||
|
||||
return new DashboardGridItem({
|
||||
key: `grid-item-${panel.spec.id}`,
|
||||
x: element.spec.x,
|
||||
y: element.spec.y,
|
||||
width: element.spec.repeat?.direction === 'h' ? 24 : element.spec.width,
|
||||
height: element.spec.height,
|
||||
itemHeight: element.spec.height,
|
||||
body: vizPanel,
|
||||
variableName: element.spec.repeat?.value,
|
||||
repeatDirection: element.spec.repeat?.direction,
|
||||
maxPerRow: element.spec.repeat?.maxPerRow,
|
||||
});
|
||||
return buildGridItem(element.spec, panel);
|
||||
} else if (panel.kind === 'LibraryPanel') {
|
||||
const libraryPanel = buildLibraryPanel(panel);
|
||||
|
||||
@ -269,7 +277,30 @@ function createSceneGridLayoutForItems(dashboard: DashboardV2Spec): SceneGridIte
|
||||
} else {
|
||||
throw new Error(`Unknown element kind: ${element.kind}`);
|
||||
}
|
||||
} else if (element.kind === 'GridLayoutRow') {
|
||||
const children = element.spec.elements.map((gridElement) => {
|
||||
const panel = dashboard.elements[gridElement.spec.element.name];
|
||||
if (panel.kind === 'Panel') {
|
||||
return buildGridItem(gridElement.spec, panel, element.spec.y + GRID_ROW_HEIGHT + gridElement.spec.y);
|
||||
} else {
|
||||
throw new Error(`Unknown element kind: ${gridElement.kind}`);
|
||||
}
|
||||
});
|
||||
let behaviors: SceneObject[] | undefined;
|
||||
if (element.spec.repeat) {
|
||||
behaviors = [new RowRepeaterBehavior({ variableName: element.spec.repeat.value })];
|
||||
}
|
||||
return new SceneGridRow({
|
||||
y: element.spec.y,
|
||||
isCollapsed: element.spec.collapsed,
|
||||
title: element.spec.title,
|
||||
$behaviors: behaviors,
|
||||
actions: new RowActions({}),
|
||||
children,
|
||||
});
|
||||
} else {
|
||||
// If this has been validated by the schema we should never reach this point, which is why TS is telling us this is an error.
|
||||
//@ts-expect-error
|
||||
throw new Error(`Unknown layout element kind: ${element.kind}`);
|
||||
}
|
||||
});
|
||||
|
@ -194,6 +194,7 @@ export function vizPanelToPanel(
|
||||
name: libPanel!.state.name,
|
||||
uid: libPanel!.state.uid,
|
||||
},
|
||||
type: 'library-panel-ref',
|
||||
} as Panel;
|
||||
|
||||
return panel;
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
IntervalVariable,
|
||||
QueryVariable,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneRefreshPicker,
|
||||
SceneTimePicker,
|
||||
SceneTimeRange,
|
||||
@ -29,6 +30,7 @@ import { DashboardControls } from '../scene/DashboardControls';
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
||||
|
||||
@ -140,6 +142,8 @@ describe('transformSceneToSaveModelSchemaV2', () => {
|
||||
isLazy: false,
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
y: 0,
|
||||
height: 10,
|
||||
body: new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'timeseries',
|
||||
@ -172,6 +176,31 @@ describe('transformSceneToSaveModelSchemaV2', () => {
|
||||
// repeatDirection?: RepeatDirection,
|
||||
// maxPerRow?: number,
|
||||
}),
|
||||
new SceneGridRow({
|
||||
key: 'panel-4',
|
||||
title: 'Test Row',
|
||||
y: 10,
|
||||
$behaviors: [new RowRepeaterBehavior({ variableName: 'customVar' })],
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
y: 11,
|
||||
body: new VizPanel({
|
||||
key: 'panel-2',
|
||||
pluginId: 'graph',
|
||||
title: 'Test Panel 2',
|
||||
description: 'Test Description 2',
|
||||
fieldConfig: { defaults: {}, overrides: [] },
|
||||
displayMode: 'transparent',
|
||||
pluginVersion: '7.0.0',
|
||||
$timeRange: new SceneTimeRange({
|
||||
timeZone: 'UTC',
|
||||
from: 'now-3h',
|
||||
to: 'now',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
dataLayers,
|
||||
SceneDataQuery,
|
||||
SceneDataTransformer,
|
||||
SceneGridRow,
|
||||
SceneVariableSet,
|
||||
VizPanel,
|
||||
} from '@grafana/scenes';
|
||||
@ -38,6 +39,7 @@ import {
|
||||
LibraryPanelKind,
|
||||
Element,
|
||||
RepeatOptions,
|
||||
GridLayoutRowKind,
|
||||
DashboardCursorSync,
|
||||
FieldConfig,
|
||||
FieldColor,
|
||||
@ -45,6 +47,7 @@ import {
|
||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
@ -57,6 +60,7 @@ import {
|
||||
calculateGridItemDimensions,
|
||||
} from '../utils/utils';
|
||||
|
||||
import { GRID_ROW_HEIGHT } from './const';
|
||||
import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables';
|
||||
import { colorIdEnumToColorIdV2, transformCursorSynctoEnum } from './transformToV2TypesUtils';
|
||||
|
||||
@ -154,9 +158,12 @@ function getLiveNow(state: DashboardSceneState) {
|
||||
return Boolean(liveNow);
|
||||
}
|
||||
|
||||
function getGridLayoutItems(state: DashboardSceneState, isSnapshot?: boolean): GridLayoutItemKind[] {
|
||||
function getGridLayoutItems(
|
||||
state: DashboardSceneState,
|
||||
isSnapshot?: boolean
|
||||
): Array<GridLayoutItemKind | GridLayoutRowKind> {
|
||||
const body = state.body;
|
||||
let elements: GridLayoutItemKind[] = [];
|
||||
let elements: Array<GridLayoutItemKind | GridLayoutRowKind> = [];
|
||||
if (body instanceof DefaultGridLayoutManager) {
|
||||
for (const child of body.state.grid.state.children) {
|
||||
if (child instanceof DashboardGridItem) {
|
||||
@ -166,23 +173,24 @@ function getGridLayoutItems(state: DashboardSceneState, isSnapshot?: boolean): G
|
||||
} else {
|
||||
elements.push(gridItemToGridLayoutItemKind(child, isSnapshot));
|
||||
}
|
||||
} else if (child instanceof SceneGridRow) {
|
||||
if (child.state.key!.indexOf('-clone-') > 0 && !isSnapshot) {
|
||||
// Skip repeat rows
|
||||
continue;
|
||||
}
|
||||
elements.push(gridRowToLayoutRowKind(child, isSnapshot));
|
||||
}
|
||||
|
||||
// TODO: OLD transformer code
|
||||
// if (child instanceof SceneGridRow) {
|
||||
// // Skip repeat clones or when generating a snapshot
|
||||
// if (child.state.key!.indexOf('-clone-') > 0 && !isSnapshot) {
|
||||
// continue;
|
||||
// }
|
||||
// gridRowToSaveModel(child, panels, isSnapshot);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
export function gridItemToGridLayoutItemKind(gridItem: DashboardGridItem, isSnapshot = false): GridLayoutItemKind {
|
||||
export function gridItemToGridLayoutItemKind(
|
||||
gridItem: DashboardGridItem,
|
||||
isSnapshot = false,
|
||||
yOverride?: number
|
||||
): GridLayoutItemKind {
|
||||
let elementGridItem: GridLayoutItemKind | undefined;
|
||||
let x = 0,
|
||||
y = 0,
|
||||
@ -208,7 +216,7 @@ export function gridItemToGridLayoutItemKind(gridItem: DashboardGridItem, isSnap
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x,
|
||||
y,
|
||||
y: yOverride ?? y,
|
||||
width: width,
|
||||
height: height,
|
||||
element: {
|
||||
@ -242,6 +250,38 @@ export function gridItemToGridLayoutItemKind(gridItem: DashboardGridItem, isSnap
|
||||
return elementGridItem;
|
||||
}
|
||||
|
||||
function getRowRepeat(row: SceneGridRow): RepeatOptions | undefined {
|
||||
if (row.state.$behaviors) {
|
||||
for (const behavior of row.state.$behaviors) {
|
||||
if (behavior instanceof RowRepeaterBehavior) {
|
||||
return { value: behavior.state.variableName, mode: 'variable' };
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function gridRowToLayoutRowKind(row: SceneGridRow, isSnapshot = false): GridLayoutRowKind {
|
||||
const children = row.state.children.map((child) => {
|
||||
if (!(child instanceof DashboardGridItem)) {
|
||||
throw new Error('Unsupported row child type');
|
||||
}
|
||||
const y = (child.state.y ?? 0) - (row.state.y ?? 0) - GRID_ROW_HEIGHT;
|
||||
return gridItemToGridLayoutItemKind(child, isSnapshot, y);
|
||||
});
|
||||
|
||||
return {
|
||||
kind: 'GridLayoutRow',
|
||||
spec: {
|
||||
title: row.state.title,
|
||||
y: row.state.y ?? 0,
|
||||
collapsed: Boolean(row.state.isCollapsed),
|
||||
elements: children,
|
||||
repeat: getRowRepeat(row),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getElements(state: DashboardSceneState) {
|
||||
const panels = state.body.getVizPanels() ?? [];
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { AnnotationQuery, DataQuery, VariableModel, VariableRefresh, Panel } from '@grafana/schema';
|
||||
import { DashboardV2Spec, PanelKind, VariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import {
|
||||
DashboardV2Spec,
|
||||
GridLayoutItemKind,
|
||||
GridLayoutItemSpec,
|
||||
GridLayoutRowSpec,
|
||||
PanelKind,
|
||||
VariableKind,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples';
|
||||
import {
|
||||
AnnoKeyCreatedBy,
|
||||
@ -310,6 +317,8 @@ describe('ResponseTransformers', () => {
|
||||
transparent: false,
|
||||
links: [],
|
||||
transformations: [],
|
||||
repeat: 'var1',
|
||||
repeatDirection: 'h',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -321,6 +330,69 @@ describe('ResponseTransformers', () => {
|
||||
},
|
||||
gridPos: { x: 0, y: 8, w: 12, h: 8 },
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'row',
|
||||
title: 'Row test title',
|
||||
gridPos: { x: 0, y: 16, w: 12, h: 1 },
|
||||
panels: [],
|
||||
collapsed: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'timeseries',
|
||||
title: 'Panel in row',
|
||||
gridPos: { x: 0, y: 17, w: 16, h: 8 },
|
||||
targets: [
|
||||
{
|
||||
refId: 'A',
|
||||
datasource: 'datasource1',
|
||||
expr: 'test-query',
|
||||
hide: false,
|
||||
},
|
||||
],
|
||||
datasource: {
|
||||
type: 'prometheus',
|
||||
uid: 'datasource1',
|
||||
},
|
||||
fieldConfig: { defaults: {}, overrides: [] },
|
||||
options: {},
|
||||
transparent: false,
|
||||
links: [],
|
||||
transformations: [],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'row',
|
||||
title: 'Collapsed row title',
|
||||
gridPos: { x: 0, y: 25, w: 12, h: 1 },
|
||||
panels: [
|
||||
{
|
||||
id: 5,
|
||||
type: 'timeseries',
|
||||
title: 'Panel in collapsed row',
|
||||
gridPos: { x: 0, y: 26, w: 16, h: 8 },
|
||||
targets: [
|
||||
{
|
||||
refId: 'A',
|
||||
datasource: 'datasource1',
|
||||
expr: 'test-query',
|
||||
hide: false,
|
||||
},
|
||||
],
|
||||
datasource: {
|
||||
type: 'prometheus',
|
||||
uid: 'datasource1',
|
||||
},
|
||||
fieldConfig: { defaults: {}, overrides: [] },
|
||||
options: {},
|
||||
transparent: false,
|
||||
links: [],
|
||||
transformations: [],
|
||||
},
|
||||
],
|
||||
collapsed: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -396,7 +468,7 @@ describe('ResponseTransformers', () => {
|
||||
expect(spec.annotations).toEqual([]);
|
||||
|
||||
// Panel
|
||||
expect(spec.layout.spec.items).toHaveLength(2);
|
||||
expect(spec.layout.spec.items).toHaveLength(4);
|
||||
expect(spec.layout.spec.items[0].spec).toEqual({
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
@ -406,6 +478,7 @@ describe('ResponseTransformers', () => {
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 8,
|
||||
repeat: { value: 'var1', direction: 'h', mode: 'variable', maxPerRow: undefined },
|
||||
});
|
||||
expect(spec.elements['1']).toEqual({
|
||||
kind: 'Panel',
|
||||
@ -481,6 +554,43 @@ describe('ResponseTransformers', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const rowSpec = spec.layout.spec.items[2].spec as GridLayoutRowSpec;
|
||||
|
||||
expect(rowSpec.collapsed).toBe(false);
|
||||
expect(rowSpec.title).toBe('Row test title');
|
||||
expect(rowSpec.repeat).toBeUndefined();
|
||||
|
||||
const panelInRow = rowSpec.elements[0].spec as GridLayoutItemSpec;
|
||||
|
||||
expect(panelInRow).toEqual({
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: '4',
|
||||
},
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 16,
|
||||
height: 8,
|
||||
});
|
||||
|
||||
const collapsedRowSpec = spec.layout.spec.items[3].spec as GridLayoutRowSpec;
|
||||
expect(collapsedRowSpec.collapsed).toBe(true);
|
||||
expect(collapsedRowSpec.title).toBe('Collapsed row title');
|
||||
expect(collapsedRowSpec.repeat).toBeUndefined();
|
||||
|
||||
const panelInCollapsedRow = collapsedRowSpec.elements[0].spec as GridLayoutItemSpec;
|
||||
|
||||
expect(panelInCollapsedRow).toEqual({
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: '5',
|
||||
},
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 16,
|
||||
height: 8,
|
||||
});
|
||||
|
||||
// Variables
|
||||
validateVariablesV1ToV2(spec.variables[0], dashboardV1.templating?.list?.[0]);
|
||||
validateVariablesV1ToV2(spec.variables[1], dashboardV1.templating?.list?.[1]);
|
||||
@ -645,6 +755,9 @@ describe('ResponseTransformers', () => {
|
||||
uid: 'uid-for-library-panel',
|
||||
name: 'Library Panel',
|
||||
});
|
||||
expect(dashboard.panels![2].type).toBe('row');
|
||||
expect(dashboard.panels![2].id).toBe(4); // Row id should be assigned to unique number following the highest id of panels.
|
||||
expect(dashboard.panels![3].type).toBe('timeseries');
|
||||
});
|
||||
|
||||
describe('getPanelQueries', () => {
|
||||
@ -756,7 +869,7 @@ describe('ResponseTransformers', () => {
|
||||
expect(v1.transformations).toEqual(v2Spec.data.spec.transformations.map((t) => t.spec));
|
||||
const layoutElement = layoutV2.spec.items.find(
|
||||
(item) => item.kind === 'GridLayoutItem' && item.spec.element.name === panelKey
|
||||
);
|
||||
) as GridLayoutItemKind;
|
||||
expect(v1.gridPos?.x).toEqual(layoutElement?.spec.x);
|
||||
expect(v1.gridPos?.y).toEqual(layoutElement?.spec.y);
|
||||
expect(v1.gridPos?.w).toEqual(layoutElement?.spec.width);
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
DataQuery,
|
||||
DataSourceRef,
|
||||
Panel,
|
||||
RowPanel,
|
||||
VariableModel,
|
||||
VariableType,
|
||||
FieldConfigSource as FieldConfigSourceV1,
|
||||
@ -34,6 +35,10 @@ import {
|
||||
IntervalVariableKind,
|
||||
TextVariableKind,
|
||||
GroupByVariableKind,
|
||||
LibraryPanelKind,
|
||||
PanelKind,
|
||||
GridLayoutRowKind,
|
||||
GridLayoutItemKind,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { DashboardLink, DataTransformerConfig } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen';
|
||||
import {
|
||||
@ -47,6 +52,7 @@ import {
|
||||
AnnoKeyUpdatedTimestamp,
|
||||
DeprecatedInternalId,
|
||||
} from 'app/features/apiserver/types';
|
||||
import { GRID_ROW_HEIGHT } from 'app/features/dashboard-scene/serialization/const';
|
||||
import { TypedVariableModelV2 } from 'app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene';
|
||||
import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2';
|
||||
import {
|
||||
@ -268,8 +274,9 @@ export const ResponseTransformers = {
|
||||
ensureV1Response,
|
||||
};
|
||||
|
||||
// TODO[schema v2]: handle rows
|
||||
function getElementsFromPanels(panels: Panel[]): [DashboardV2Spec['elements'], DashboardV2Spec['layout']] {
|
||||
function getElementsFromPanels(
|
||||
panels: Array<Panel | RowPanel>
|
||||
): [DashboardV2Spec['elements'], DashboardV2Spec['layout']] {
|
||||
const elements: DashboardV2Spec['elements'] = {};
|
||||
const layout: DashboardV2Spec['layout'] = {
|
||||
kind: 'GridLayout',
|
||||
@ -282,94 +289,156 @@ function getElementsFromPanels(panels: Panel[]): [DashboardV2Spec['elements'], D
|
||||
return [elements, layout];
|
||||
}
|
||||
|
||||
let currentRow: GridLayoutRowKind | null = null;
|
||||
|
||||
// iterate over panels
|
||||
for (const p of panels) {
|
||||
const elementName = p.id!.toString();
|
||||
|
||||
// LibraryPanelKind
|
||||
if (p.libraryPanel) {
|
||||
elements[elementName] = {
|
||||
kind: 'LibraryPanel',
|
||||
spec: {
|
||||
libraryPanel: {
|
||||
uid: p.libraryPanel.uid,
|
||||
name: p.libraryPanel.name,
|
||||
},
|
||||
id: p.id!,
|
||||
title: p.title ?? '',
|
||||
},
|
||||
};
|
||||
// PanelKind
|
||||
} else {
|
||||
// FIXME: for now we should skip row panels
|
||||
if (p.type === 'row') {
|
||||
continue;
|
||||
if (isRowPanel(p)) {
|
||||
if (currentRow) {
|
||||
// Flush current row to layout before we create a new one
|
||||
layout.spec.items.push(currentRow);
|
||||
}
|
||||
|
||||
const queries = getPanelQueries(
|
||||
(p.targets as unknown as DataQuery[]) || [],
|
||||
p.datasource || getDefaultDatasource()
|
||||
);
|
||||
const transformations = getPanelTransformations(p.transformations || []);
|
||||
const rowElements = [];
|
||||
|
||||
elements[elementName] = {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
title: p.title || '',
|
||||
description: p.description || '',
|
||||
vizConfig: {
|
||||
kind: p.type,
|
||||
spec: {
|
||||
fieldConfig: (p.fieldConfig as any) || defaultFieldConfigSource(),
|
||||
options: p.options as any,
|
||||
pluginVersion: p.pluginVersion!,
|
||||
},
|
||||
},
|
||||
links:
|
||||
p.links?.map<DataLink>((l) => ({
|
||||
title: l.title,
|
||||
url: l.url || '',
|
||||
targetBlank: l.targetBlank,
|
||||
})) || [],
|
||||
id: p.id!,
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries,
|
||||
transformations, // TODO[schema v2]: handle transformations
|
||||
queryOptions: {
|
||||
cacheTimeout: p.cacheTimeout,
|
||||
maxDataPoints: p.maxDataPoints,
|
||||
interval: p.interval,
|
||||
hideTimeOverride: p.hideTimeOverride,
|
||||
queryCachingTTL: p.queryCachingTTL,
|
||||
timeFrom: p.timeFrom,
|
||||
timeShift: p.timeShift,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
for (const panel of p.panels) {
|
||||
const [element, name] = buildElement(panel);
|
||||
elements[name] = element;
|
||||
rowElements.push(buildGridItemKind(panel, name, yOffsetInRows(panel, p.gridPos!.y)));
|
||||
}
|
||||
|
||||
currentRow = buildRowKind(p, rowElements);
|
||||
} else {
|
||||
const [element, elementName] = buildElement(p);
|
||||
|
||||
elements[elementName] = element;
|
||||
|
||||
if (currentRow) {
|
||||
// Collect panels to current layout row
|
||||
currentRow.spec.elements.push(buildGridItemKind(p, elementName, yOffsetInRows(p, currentRow.spec.y)));
|
||||
} else {
|
||||
layout.spec.items.push(buildGridItemKind(p, elementName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout.spec.items.push({
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x: p.gridPos!.x,
|
||||
y: p.gridPos!.y,
|
||||
width: p.gridPos!.w,
|
||||
height: p.gridPos!.h,
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: elementName,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (currentRow) {
|
||||
// Flush last row to layout
|
||||
layout.spec.items.push(currentRow);
|
||||
}
|
||||
|
||||
return [elements, layout];
|
||||
}
|
||||
|
||||
function isRowPanel(panel: Panel | RowPanel): panel is RowPanel {
|
||||
return panel.type === 'row';
|
||||
}
|
||||
|
||||
function buildRowKind(p: RowPanel, elements: GridLayoutItemKind[]): GridLayoutRowKind {
|
||||
return {
|
||||
kind: 'GridLayoutRow',
|
||||
spec: {
|
||||
collapsed: p.collapsed,
|
||||
title: p.title ?? '',
|
||||
repeat: p.repeat ? { value: p.repeat, mode: 'variable' } : undefined,
|
||||
y: p.gridPos?.y ?? 0,
|
||||
elements,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildGridItemKind(p: Panel, elementName: string, yOverride?: number): GridLayoutItemKind {
|
||||
return {
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x: p.gridPos!.x,
|
||||
y: yOverride ?? p.gridPos!.y,
|
||||
width: p.gridPos!.w,
|
||||
height: p.gridPos!.h,
|
||||
repeat: p.repeat
|
||||
? { value: p.repeat, mode: 'variable', direction: p.repeatDirection, maxPerRow: p.maxPerRow }
|
||||
: undefined,
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: elementName!,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function yOffsetInRows(p: Panel, rowY: number): number {
|
||||
return p.gridPos!.y - rowY - GRID_ROW_HEIGHT;
|
||||
}
|
||||
|
||||
function buildElement(p: Panel): [PanelKind | LibraryPanelKind, string] {
|
||||
if (p.libraryPanel) {
|
||||
// LibraryPanelKind
|
||||
const panelKind: LibraryPanelKind = {
|
||||
kind: 'LibraryPanel',
|
||||
spec: {
|
||||
libraryPanel: {
|
||||
uid: p.libraryPanel.uid,
|
||||
name: p.libraryPanel.name,
|
||||
},
|
||||
id: p.id!,
|
||||
title: p.title ?? '',
|
||||
},
|
||||
};
|
||||
|
||||
return [panelKind, p.id!.toString()];
|
||||
} else {
|
||||
// PanelKind
|
||||
|
||||
const queries = getPanelQueries(
|
||||
(p.targets as unknown as DataQuery[]) || [],
|
||||
p.datasource || getDefaultDatasource()
|
||||
);
|
||||
|
||||
const transformations = getPanelTransformations(p.transformations || []);
|
||||
|
||||
const panelKind: PanelKind = {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
title: p.title || '',
|
||||
description: p.description || '',
|
||||
vizConfig: {
|
||||
kind: p.type,
|
||||
spec: {
|
||||
fieldConfig: (p.fieldConfig as any) || defaultFieldConfigSource(),
|
||||
options: p.options as any,
|
||||
pluginVersion: p.pluginVersion!,
|
||||
},
|
||||
},
|
||||
links:
|
||||
p.links?.map<DataLink>((l) => ({
|
||||
title: l.title,
|
||||
url: l.url || '',
|
||||
targetBlank: l.targetBlank,
|
||||
})) || [],
|
||||
id: p.id!,
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries,
|
||||
transformations,
|
||||
queryOptions: {
|
||||
cacheTimeout: p.cacheTimeout,
|
||||
maxDataPoints: p.maxDataPoints,
|
||||
interval: p.interval,
|
||||
hideTimeOverride: p.hideTimeOverride,
|
||||
queryCachingTTL: p.queryCachingTTL,
|
||||
timeFrom: p.timeFrom,
|
||||
timeShift: p.timeShift,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return [panelKind, p.id!.toString()];
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultDatasourceType() {
|
||||
// if there is no default datasource, return 'grafana' as default
|
||||
return getDefaultDataSourceRef()?.type ?? 'grafana';
|
||||
@ -778,72 +847,130 @@ function getAnnotationsV1(annotations: DashboardV2Spec['annotations']): Annotati
|
||||
});
|
||||
}
|
||||
|
||||
interface LibraryPanelDTO extends Pick<Panel, 'libraryPanel' | 'id' | 'title' | 'gridPos'> {}
|
||||
interface LibraryPanelDTO extends Pick<Panel, 'libraryPanel' | 'id' | 'title' | 'gridPos' | 'type'> {}
|
||||
|
||||
function getPanelsV1(
|
||||
panels: DashboardV2Spec['elements'],
|
||||
layout: DashboardV2Spec['layout']
|
||||
): Array<Panel | LibraryPanelDTO> {
|
||||
return Object.entries(panels).map(([key, p]) => {
|
||||
const layoutElement = layout.spec.items.find(
|
||||
(item) => item.kind === 'GridLayoutItem' && item.spec.element.name === key
|
||||
);
|
||||
const { x, y, width, height, repeat } = layoutElement?.spec || { x: 0, y: 0, width: 0, height: 0 };
|
||||
const gridPos = { x, y, w: width, h: height };
|
||||
if (p.kind === 'Panel') {
|
||||
const panel = p.spec;
|
||||
return {
|
||||
id: panel.id,
|
||||
type: panel.vizConfig.kind,
|
||||
title: panel.title,
|
||||
description: panel.description,
|
||||
fieldConfig: transformMappingsToV1(panel.vizConfig.spec.fieldConfig),
|
||||
options: panel.vizConfig.spec.options,
|
||||
pluginVersion: panel.vizConfig.spec.pluginVersion,
|
||||
links:
|
||||
// @ts-expect-error - Panel link is wrongly typed as DashboardLink
|
||||
panel.links?.map<DashboardLink>((l) => ({
|
||||
title: l.title,
|
||||
url: l.url,
|
||||
...(l.targetBlank && { targetBlank: l.targetBlank }),
|
||||
})) || [],
|
||||
targets: panel.data.spec.queries.map((q) => {
|
||||
return {
|
||||
refId: q.spec.refId,
|
||||
hide: q.spec.hidden,
|
||||
datasource: q.spec.datasource,
|
||||
...q.spec.query.spec,
|
||||
};
|
||||
}),
|
||||
transformations: panel.data.spec.transformations.map((t) => t.spec),
|
||||
gridPos,
|
||||
cacheTimeout: panel.data.spec.queryOptions.cacheTimeout,
|
||||
maxDataPoints: panel.data.spec.queryOptions.maxDataPoints,
|
||||
interval: panel.data.spec.queryOptions.interval,
|
||||
hideTimeOverride: panel.data.spec.queryOptions.hideTimeOverride,
|
||||
queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL,
|
||||
timeFrom: panel.data.spec.queryOptions.timeFrom,
|
||||
timeShift: panel.data.spec.queryOptions.timeShift,
|
||||
transparent: panel.transparent,
|
||||
...(repeat?.value && { repeat: repeat.value }),
|
||||
...(repeat?.direction && { repeatDirection: repeat.direction }),
|
||||
...(repeat?.maxPerRow && { maxPerRow: repeat.maxPerRow }),
|
||||
};
|
||||
} else if (p.kind === 'LibraryPanel') {
|
||||
const panel = p.spec;
|
||||
return {
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
gridPos,
|
||||
libraryPanel: {
|
||||
uid: panel.libraryPanel.uid,
|
||||
name: panel.libraryPanel.name,
|
||||
const panelsV1: Array<Panel | LibraryPanelDTO | RowPanel> = [];
|
||||
|
||||
let maxPanelId = 0;
|
||||
|
||||
for (const item of layout.spec.items) {
|
||||
if (item.kind === 'GridLayoutItem') {
|
||||
const panel = panels[item.spec.element.name];
|
||||
const v1Panel = transformV2PanelToV1Panel(panel, item);
|
||||
panelsV1.push(v1Panel);
|
||||
if (v1Panel.id ?? 0 > maxPanelId) {
|
||||
maxPanelId = v1Panel.id ?? 0;
|
||||
}
|
||||
} else if (item.kind === 'GridLayoutRow') {
|
||||
const row: RowPanel = {
|
||||
id: -1, // Temporarily set to -1, updated later to be unique
|
||||
type: 'row',
|
||||
title: item.spec.title,
|
||||
collapsed: item.spec.collapsed,
|
||||
repeat: item.spec.repeat ? item.spec.repeat.value : undefined,
|
||||
gridPos: {
|
||||
x: 0,
|
||||
y: item.spec.y,
|
||||
w: 24,
|
||||
h: GRID_ROW_HEIGHT,
|
||||
},
|
||||
panels: [],
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unknown element kind: ${p}`);
|
||||
|
||||
const rowPanels = [];
|
||||
for (const panel of item.spec.elements) {
|
||||
const panelElement = panels[panel.spec.element.name];
|
||||
const v1Panel = transformV2PanelToV1Panel(panelElement, panel, item.spec.y + GRID_ROW_HEIGHT + panel.spec.y);
|
||||
rowPanels.push(v1Panel);
|
||||
if (v1Panel.id ?? 0 > maxPanelId) {
|
||||
maxPanelId = v1Panel.id ?? 0;
|
||||
}
|
||||
}
|
||||
if (item.spec.collapsed) {
|
||||
// When a row is collapsed, panels inside it are stored in the panels property.
|
||||
row.panels = rowPanels;
|
||||
panelsV1.push(row);
|
||||
} else {
|
||||
panelsV1.push(row);
|
||||
panelsV1.push(...rowPanels);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update row panel ids to be unique
|
||||
for (const panel of panelsV1) {
|
||||
if (panel.type === 'row' && panel.id === -1) {
|
||||
panel.id = ++maxPanelId;
|
||||
}
|
||||
}
|
||||
return panelsV1;
|
||||
}
|
||||
|
||||
function transformV2PanelToV1Panel(
|
||||
p: PanelKind | LibraryPanelKind,
|
||||
layoutElement: GridLayoutItemKind,
|
||||
yOverride?: number
|
||||
): Panel | LibraryPanelDTO {
|
||||
const { x, y, width, height, repeat } = layoutElement?.spec || { x: 0, y: 0, width: 0, height: 0 };
|
||||
const gridPos = { x, y: yOverride ?? y, w: width, h: height };
|
||||
if (p.kind === 'Panel') {
|
||||
const panel = p.spec;
|
||||
return {
|
||||
id: panel.id,
|
||||
type: panel.vizConfig.kind,
|
||||
title: panel.title,
|
||||
description: panel.description,
|
||||
fieldConfig: transformMappingsToV1(panel.vizConfig.spec.fieldConfig),
|
||||
options: panel.vizConfig.spec.options,
|
||||
pluginVersion: panel.vizConfig.spec.pluginVersion,
|
||||
links:
|
||||
// @ts-expect-error - Panel link is wrongly typed as DashboardLink
|
||||
panel.links?.map<DashboardLink>((l) => ({
|
||||
title: l.title,
|
||||
url: l.url,
|
||||
...(l.targetBlank && { targetBlank: l.targetBlank }),
|
||||
})) || [],
|
||||
targets: panel.data.spec.queries.map((q) => {
|
||||
return {
|
||||
refId: q.spec.refId,
|
||||
hide: q.spec.hidden,
|
||||
datasource: q.spec.datasource,
|
||||
...q.spec.query.spec,
|
||||
};
|
||||
}),
|
||||
transformations: panel.data.spec.transformations.map((t) => t.spec),
|
||||
gridPos,
|
||||
cacheTimeout: panel.data.spec.queryOptions.cacheTimeout,
|
||||
maxDataPoints: panel.data.spec.queryOptions.maxDataPoints,
|
||||
interval: panel.data.spec.queryOptions.interval,
|
||||
hideTimeOverride: panel.data.spec.queryOptions.hideTimeOverride,
|
||||
queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL,
|
||||
timeFrom: panel.data.spec.queryOptions.timeFrom,
|
||||
timeShift: panel.data.spec.queryOptions.timeShift,
|
||||
transparent: panel.transparent,
|
||||
...(repeat?.value && { repeat: repeat.value }),
|
||||
...(repeat?.direction && { repeatDirection: repeat.direction }),
|
||||
...(repeat?.maxPerRow && { maxPerRow: repeat.maxPerRow }),
|
||||
};
|
||||
} else if (p.kind === 'LibraryPanel') {
|
||||
const panel = p.spec;
|
||||
return {
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
gridPos,
|
||||
libraryPanel: {
|
||||
uid: panel.libraryPanel.uid,
|
||||
name: panel.libraryPanel.name,
|
||||
},
|
||||
type: 'library-panel-ref',
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unknown element kind: ${p}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function transformMappingsToV1(fieldConfig: FieldConfigSource): FieldConfigSourceV1 {
|
||||
|
@ -7,6 +7,8 @@ import { SaveDashboardCommand } from '../components/SaveDashboard/types';
|
||||
|
||||
import { DashboardWithAccessInfo } from './types';
|
||||
|
||||
export const GRID_ROW_HEIGHT = 1;
|
||||
|
||||
export function getDashboardsApiVersion() {
|
||||
const forcingOldDashboardArch = locationService.getSearch().get('scenes') === 'false';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user