Dynamic dashboards: Add row repeat serialization/deserialization (#100826)
* add row repeat serialization/deserialization * prettier * Add tests for deserializing repeated rows. Also add tests for serilizing that was missing
This commit is contained in:
+192
-2
@@ -1,7 +1,10 @@
|
||||
import { SceneCSSGridLayout, SceneGridLayout } from '@grafana/scenes';
|
||||
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
|
||||
import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
|
||||
import { ResponsiveGridLayoutManager } from '../../scene/layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||
import { RowItem } from '../../scene/layout-rows/RowItem';
|
||||
import { RowItemRepeaterBehavior } from '../../scene/layout-rows/RowItemRepeaterBehavior';
|
||||
import { RowsLayoutManager } from '../../scene/layout-rows/RowsLayoutManager';
|
||||
|
||||
import { RowsLayoutSerializer } from './RowsLayoutSerializer';
|
||||
@@ -39,7 +42,14 @@ describe('deserialization', () => {
|
||||
spec: {
|
||||
title: 'Row 1',
|
||||
collapsed: false,
|
||||
layout: { kind: 'ResponsiveGridLayout', spec: { row: '', col: '', items: [] } },
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
row: 'minmax(min-content, max-content)',
|
||||
col: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -61,7 +71,14 @@ describe('deserialization', () => {
|
||||
spec: {
|
||||
title: 'Row 1',
|
||||
collapsed: false,
|
||||
layout: { kind: 'ResponsiveGridLayout', spec: { row: '', col: '', items: [] } },
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
row: 'minmax(min-content, max-content)',
|
||||
col: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -97,4 +114,177 @@ describe('deserialization', () => {
|
||||
expect(deserialized).toBeInstanceOf(RowsLayoutManager);
|
||||
expect(deserialized.state.rows).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should deserialize row with repeat behavior', () => {
|
||||
const layout: DashboardV2Spec['layout'] = {
|
||||
kind: 'RowsLayout',
|
||||
spec: {
|
||||
rows: [
|
||||
{
|
||||
kind: 'RowsLayoutRow',
|
||||
spec: {
|
||||
title: 'Repeated Row',
|
||||
collapsed: false,
|
||||
layout: { kind: 'GridLayout', spec: { items: [] } },
|
||||
repeat: { value: 'foo', mode: 'variable' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const serializer = new RowsLayoutSerializer();
|
||||
const deserialized = serializer.deserialize(layout, {}, false);
|
||||
|
||||
expect(deserialized).toBeInstanceOf(RowsLayoutManager);
|
||||
expect(deserialized.state.rows).toHaveLength(1);
|
||||
|
||||
const row = deserialized.state.rows[0];
|
||||
expect(row.state.$behaviors).toBeDefined();
|
||||
const behaviors = row.state.$behaviors ?? [];
|
||||
expect(behaviors).toHaveLength(1);
|
||||
const repeaterBehavior = behaviors[0] as RowItemRepeaterBehavior;
|
||||
expect(repeaterBehavior).toBeInstanceOf(RowItemRepeaterBehavior);
|
||||
expect(repeaterBehavior.state.variableName).toBe('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('serialization', () => {
|
||||
it('should serialize basic row layout', () => {
|
||||
const rowsLayout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({
|
||||
title: 'Row 1',
|
||||
isCollapsed: false,
|
||||
layout: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [],
|
||||
isDraggable: true,
|
||||
isResizable: true,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const serializer = new RowsLayoutSerializer();
|
||||
const serialized = serializer.serialize(rowsLayout);
|
||||
|
||||
expect(serialized).toEqual({
|
||||
kind: 'RowsLayout',
|
||||
spec: {
|
||||
rows: [
|
||||
{
|
||||
kind: 'RowsLayoutRow',
|
||||
spec: {
|
||||
title: 'Row 1',
|
||||
collapsed: false,
|
||||
layout: { kind: 'GridLayout', spec: { items: [] } },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should serialize row with repeat behavior', () => {
|
||||
const rowsLayout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({
|
||||
title: 'Repeated Row',
|
||||
isCollapsed: false,
|
||||
layout: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [],
|
||||
isDraggable: true,
|
||||
isResizable: true,
|
||||
}),
|
||||
}),
|
||||
$behaviors: [new RowItemRepeaterBehavior({ variableName: 'foo' })],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const serializer = new RowsLayoutSerializer();
|
||||
const serialized = serializer.serialize(rowsLayout);
|
||||
|
||||
expect(serialized).toEqual({
|
||||
kind: 'RowsLayout',
|
||||
spec: {
|
||||
rows: [
|
||||
{
|
||||
kind: 'RowsLayoutRow',
|
||||
spec: {
|
||||
title: 'Repeated Row',
|
||||
collapsed: false,
|
||||
layout: { kind: 'GridLayout', spec: { items: [] } },
|
||||
repeat: { value: 'foo', mode: 'variable' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should serialize multiple rows with different layouts', () => {
|
||||
const rowsLayout = new RowsLayoutManager({
|
||||
rows: [
|
||||
new RowItem({
|
||||
title: 'Row 1',
|
||||
isCollapsed: false,
|
||||
layout: new ResponsiveGridLayoutManager({
|
||||
layout: new SceneCSSGridLayout({
|
||||
children: [],
|
||||
templateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
autoRows: 'minmax(min-content, max-content)',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
new RowItem({
|
||||
title: 'Row 2',
|
||||
isCollapsed: true,
|
||||
layout: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [],
|
||||
isDraggable: true,
|
||||
isResizable: true,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const serializer = new RowsLayoutSerializer();
|
||||
const serialized = serializer.serialize(rowsLayout);
|
||||
|
||||
expect(serialized).toEqual({
|
||||
kind: 'RowsLayout',
|
||||
spec: {
|
||||
rows: [
|
||||
{
|
||||
kind: 'RowsLayoutRow',
|
||||
spec: {
|
||||
title: 'Row 1',
|
||||
collapsed: false,
|
||||
layout: {
|
||||
kind: 'ResponsiveGridLayout',
|
||||
spec: {
|
||||
row: 'minmax(min-content, max-content)',
|
||||
col: 'repeat(auto-fit, minmax(400px, 1fr))',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'RowsLayoutRow',
|
||||
spec: {
|
||||
title: 'Row 2',
|
||||
collapsed: true,
|
||||
layout: { kind: 'GridLayout', spec: { items: [] } },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+21
-2
@@ -1,6 +1,8 @@
|
||||
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { DashboardV2Spec, RowsLayoutRowKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
|
||||
import { RowItem } from '../../scene/layout-rows/RowItem';
|
||||
import { RowItemRepeaterBehavior } from '../../scene/layout-rows/RowItemRepeaterBehavior';
|
||||
import { RowsLayoutManager } from '../../scene/layout-rows/RowsLayoutManager';
|
||||
import { LayoutManagerSerializer } from '../../scene/types/DashboardLayoutManager';
|
||||
|
||||
@@ -17,7 +19,7 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer {
|
||||
if (layout.kind === 'RowsLayout') {
|
||||
throw new Error('Nested RowsLayout is not supported');
|
||||
}
|
||||
return {
|
||||
const rowKind: RowsLayoutRowKind = {
|
||||
kind: 'RowsLayoutRow',
|
||||
spec: {
|
||||
title: row.state.title,
|
||||
@@ -25,6 +27,18 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer {
|
||||
layout: layout,
|
||||
},
|
||||
};
|
||||
|
||||
if (row.state.$behaviors) {
|
||||
for (const behavior of row.state.$behaviors) {
|
||||
if (behavior instanceof RowItemRepeaterBehavior) {
|
||||
if (rowKind.spec.repeat) {
|
||||
throw new Error('Multiple repeaters are not supported');
|
||||
}
|
||||
rowKind.spec.repeat = { value: behavior.state.variableName, mode: 'variable' };
|
||||
}
|
||||
}
|
||||
}
|
||||
return rowKind;
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -40,9 +54,14 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer {
|
||||
}
|
||||
const rows = layout.spec.rows.map((row) => {
|
||||
const layout = row.spec.layout;
|
||||
const behaviors: SceneObject[] = [];
|
||||
if (row.spec.repeat) {
|
||||
behaviors.push(new RowItemRepeaterBehavior({ variableName: row.spec.repeat.value }));
|
||||
}
|
||||
return new RowItem({
|
||||
title: row.spec.title,
|
||||
isCollapsed: row.spec.collapsed,
|
||||
$behaviors: behaviors,
|
||||
layout: layoutSerializerRegistry.get(layout.kind).serializer.deserialize(layout, elements, preload),
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user