import { advanceTo } from 'jest-date-mock'; import { map, of } from 'rxjs'; import { DataFrame, DataQueryRequest, DataSourceApi, dateTime, FieldType, PanelData, standardTransformersRegistry, StandardVariableQuery, toDataFrame, VariableSupportType, } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { getPluginLinkExtensions, setPluginImportUtils } from '@grafana/runtime'; import { MultiValueVariable, sceneGraph, 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 { DashboardDataDTO } from 'app/types'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { NEW_LINK } from '../settings/links/utils'; import { activateFullSceneTree, buildPanelRepeaterScene } from '../utils/test-utils'; import { getVizPanelKeyForPanelId } from '../utils/utils'; import { GRAFANA_DATASOURCE_REF } from './const'; import dashboard_to_load1 from './testfiles/dashboard_to_load1.json'; import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json'; import snapshotableDashboardJson from './testfiles/snapshotable_dashboard.json'; import snapshotableWithRowsDashboardJson from './testfiles/snapshotable_with_rows.json'; import { buildGridItemForPanel, transformSaveModelToScene } from './transformSaveModelToScene'; import { gridItemToPanel, gridRowToSaveModel, panelRepeaterToPanels, transformSceneToSaveModel, trimDashboardForSnapshot, } from './transformSceneToSaveModel'; standardTransformersRegistry.setInit(() => [reduceTransformRegistryItem]); setPluginImportUtils({ importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), getPanelPluginFromCache: (id: string) => undefined, }); const AFrame = toDataFrame({ name: 'A', fields: [ { name: 'time', type: FieldType.time, values: [100, 200, 300] }, { name: 'values', type: FieldType.number, values: [1, 2, 3] }, ], }); const BFrame = toDataFrame({ name: 'B', fields: [ { name: 'time', type: FieldType.time, values: [100, 200, 300] }, { name: 'values', type: FieldType.number, values: [10, 20, 30] }, ], }); const CFrame = toDataFrame({ name: 'C', fields: [ { name: 'time', type: FieldType.time, values: [1000, 2000, 3000] }, { name: 'values', type: FieldType.number, values: [100, 200, 300] }, ], }); const AnnoFrame = toDataFrame({ fields: [ { name: 'time', values: [1, 2, 2, 5, 5] }, { name: 'id', values: ['1', '2', '2', '5', '5'] }, { name: 'text', values: ['t1', 't2', 't3', 't4', 't5'] }, ], }); const VariableQueryFrame = toDataFrame({ fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }], }); const testSeries: Record = { A: AFrame, B: BFrame, C: CFrame, Anno: AnnoFrame, VariableQuery: VariableQueryFrame, }; const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request: DataQueryRequest) => { const result: PanelData = { state: LoadingState.Loading, series: [], timeRange: request.range, }; return of([]).pipe( map(() => { result.state = LoadingState.Done; const refId = request.targets[0].refId; result.series = [testSeries[refId]]; return result; }) ); }); jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getDataSourceSrv: () => ({ get: () => ({ getRef: () => ({ type: 'mock-ds', uid: 'mock-uid' }), variables: { getType: () => VariableSupportType.Standard, toDataQuery: (q: StandardVariableQuery) => q, }, }), // mock getInstanceSettings() getInstanceSettings: jest.fn(), }), getRunRequest: () => (ds: DataSourceApi, request: DataQueryRequest) => { return runRequestMock(ds, request); }, config: { ...jest.requireActual('@grafana/runtime').config, panels: { text: { skipDataQuery: true }, }, featureToggles: { dataTrails: false, }, theme2: { visualization: { getColorByName: jest.fn().mockReturnValue('red'), }, }, }, setPluginExtensionGetter: jest.fn(), getPluginLinkExtensions: jest.fn(), })); const getPluginLinkExtensionsMock = jest.mocked(getPluginLinkExtensions); jest.mock('@grafana/scenes', () => ({ ...jest.requireActual('@grafana/scenes'), sceneUtils: { ...jest.requireActual('@grafana/scenes').sceneUtils, registerVariableMacro: jest.fn(), }, })); describe('transformSceneToSaveModel', () => { beforeEach(() => { getPluginLinkExtensionsMock.mockRestore(); getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] }); }); describe('Given a simple scene with custom settings', () => { it('Should transform back to persisted model', () => { const dashboardWithCustomSettings = { ...dashboard_to_load1, title: 'My custom title', description: 'My custom description', tags: ['tag1', 'tag2'], timezone: 'America/New_York', weekStart: 'monday', graphTooltip: 1, editable: false, refresh: '5m', timepicker: { ...dashboard_to_load1.timepicker, refresh_intervals: ['5m', '15m', '30m', '1h'], time_options: ['5m', '15m', '30m'], hidden: true, }, links: [{ ...NEW_LINK, title: 'Link 1' }], }; const scene = transformSaveModelToScene({ dashboard: dashboardWithCustomSettings as DashboardDataDTO, meta: {} }); const saveModel = transformSceneToSaveModel(scene); expect(saveModel).toMatchSnapshot(); }); }); describe('Given a simple scene with variables', () => { it('Should transform back to persisted model', () => { const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} }); const saveModel = transformSceneToSaveModel(scene); expect(saveModel).toMatchSnapshot(); }); }); describe('Given a scene with rows', () => { it('Should transform back to persisted model', () => { const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO, meta: {}, }); const saveModel = transformSceneToSaveModel(scene); const row2: RowPanel = saveModel.panels![2] as RowPanel; expect(row2.type).toBe('row'); expect(row2.repeat).toBe('server'); expect(saveModel).toMatchSnapshot(); }); it('Should remove repeated rows in save model', () => { const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO, meta: {}, }); const variable = scene.state.$variables?.state.variables[0] as MultiValueVariable; variable.changeValueTo(['a', 'b', 'c']); const grid = scene.state.body as SceneGridLayout; const rowWithRepeat = grid.state.children[1] as SceneGridRow; const rowRepeater = rowWithRepeat.state.$behaviors![0] as RowRepeaterBehavior; // trigger row repeater rowRepeater.performRepeat(); // Make sure the repeated rows have been added to runtime scene model expect(grid.state.children.length).toBe(5); const saveModel = transformSceneToSaveModel(scene); const rows = saveModel.panels!.filter((p) => p.type === 'row'); // Verify the save model does not contain any repeated rows expect(rows.length).toBe(3); }); }); describe('Panel options', () => { it('Given panel with time override', () => { const gridItem = buildGridItemFromPanelSchema({ timeFrom: '2h', timeShift: '1d', hideTimeOverride: true, }); const saveModel = gridItemToPanel(gridItem); expect(saveModel.timeFrom).toBe('2h'); expect(saveModel.timeShift).toBe('1d'); expect(saveModel.hideTimeOverride).toBe(true); }); it('transparent panel', () => { const gridItem = buildGridItemFromPanelSchema({ transparent: true }); const saveModel = gridItemToPanel(gridItem); expect(saveModel.transparent).toBe(true); }); it('interval', () => { const gridItem = buildGridItemFromPanelSchema({ interval: '20m' }); const saveModel = gridItemToPanel(gridItem); expect(saveModel.interval).toBe('20m'); }); it('With angular options', () => { const gridItem = buildGridItemFromPanelSchema({}); const vizPanel = gridItem.state.body as VizPanel; vizPanel.setState({ options: { angularOptions: { bars: true, }, }, }); const saveModel = gridItemToPanel(gridItem); expect(saveModel.options?.angularOptions).toBe(undefined); expect((saveModel as any).bars).toBe(true); }); it('Given panel with repeat', () => { const gridItem = buildGridItemFromPanelSchema({ title: '', type: 'text-plugin-34', gridPos: { x: 1, y: 2, w: 12, h: 8 }, repeat: 'server', repeatDirection: 'v', maxPerRow: 8, }); const saveModel = gridItemToPanel(gridItem); expect(saveModel.repeat).toBe('server'); expect(saveModel.repeatDirection).toBe('v'); expect(saveModel.maxPerRow).toBe(8); expect(saveModel.gridPos?.x).toBe(1); expect(saveModel.gridPos?.y).toBe(2); expect(saveModel.gridPos?.w).toBe(12); expect(saveModel.gridPos?.h).toBe(8); }); it('Given panel with links', () => { const gridItem = buildGridItemFromPanelSchema({ title: '', type: 'text-plugin-34', gridPos: { x: 1, y: 2, w: 12, h: 8 }, links: [ // @ts-expect-error Panel link is wrongly typed as DashboardLink { title: 'Link 1', url: 'http://some.test.link1', }, // @ts-expect-error Panel link is wrongly typed as DashboardLink { targetBlank: true, title: 'Link 2', url: 'http://some.test.link2', }, ], }); const saveModel = gridItemToPanel(gridItem); expect(saveModel.links).toEqual([ { title: 'Link 1', url: 'http://some.test.link1', }, { targetBlank: true, title: 'Link 2', url: 'http://some.test.link2', }, ]); }); }); describe('Library panels', () => { it('given a library panel', () => { const libVizPanel = new VizPanel({ key: 'panel-4', title: 'Panel blahh blah', $behaviors: [ new LibraryPanelBehavior({ name: 'Some lib panel panel', title: 'A panel', uid: 'lib-panel-uid', }), ], fieldConfig: { defaults: {}, overrides: [], }, options: { legend: { calcs: [], displayMode: 'list', placement: 'bottom', showLegend: true, }, tooltip: { maxHeight: 600, mode: 'single', sort: 'none', }, }, }); const panel = new DashboardGridItem({ body: libVizPanel, y: 0, x: 0, width: 12, height: 8, }); const result = gridItemToPanel(panel); expect(result.id).toBe(4); expect(result.libraryPanel).toEqual({ name: 'Some lib panel panel', uid: 'lib-panel-uid', }); expect(result.gridPos).toEqual({ h: 8, w: 12, x: 0, y: 0, }); expect(result.title).toBe('A panel'); expect(result.transformations).toBeUndefined(); expect(result.fieldConfig).toBeUndefined(); expect(result.options).toBeUndefined(); }); it('given a library panel widget', () => { const panel = buildGridItemFromPanelSchema({ id: 4, gridPos: { h: 8, w: 12, x: 0, y: 0, }, type: 'add-library-panel', }); const result = gridItemToPanel(panel); expect(result.id).toBe(4); expect(result.gridPos).toEqual({ h: 8, w: 12, x: 0, y: 0, }); expect(result.type).toBe('add-library-panel'); }); }); describe('Annotations', () => { it('should transform annotations to save model', () => { const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} }); const saveModel = transformSceneToSaveModel(scene); 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 DashboardDataDTO, meta: {} }); const layers = (scene.state.$data as DashboardDataLayerSet)?.state.annotationLayers; const enabledLayer = layers[1]; const hiddenLayer = layers[3]; enabledLayer.setState({ isEnabled: false, }); hiddenLayer.setState({ isHidden: false, }); const saveModel = transformSceneToSaveModel(scene); expect(saveModel.annotations?.list?.length).toBe(4); expect(saveModel.annotations?.list?.[1].enable).toEqual(false); expect(saveModel.annotations?.list?.[3].hide).toEqual(false); }); }); describe('Queries', () => { it('Given panel with queries', () => { const panel = buildGridItemFromPanelSchema({ datasource: { type: 'grafana-testdata', uid: 'abc', }, maxDataPoints: 100, targets: [ { refId: 'A', expr: 'A', datasource: { type: 'grafana-testdata', uid: 'abc', }, }, { refId: 'B', expr: 'B', }, ], }); const result = gridItemToPanel(panel); expect(result.maxDataPoints).toBe(100); expect(result.targets?.length).toBe(2); expect(result.targets?.[0]).toEqual({ refId: 'A', expr: 'A', datasource: { type: 'grafana-testdata', uid: 'abc', }, }); expect(result.datasource).toEqual({ type: 'grafana-testdata', uid: 'abc', }); }); it('Given panel with transformations', () => { const panel = buildGridItemFromPanelSchema({ datasource: { type: 'grafana-testdata', uid: 'abc', }, maxDataPoints: 100, transformations: [ { id: 'reduce', options: { reducers: ['max'], mode: 'reduceFields', includeTimeField: false, }, }, ], targets: [ { refId: 'A', expr: 'A', datasource: { type: 'grafana-testdata', uid: 'abc', }, }, { refId: 'B', expr: 'B', }, ], }); const result = gridItemToPanel(panel); expect(result.transformations?.length).toBe(1); expect(result.maxDataPoints).toBe(100); expect(result.targets?.length).toBe(2); expect(result.targets?.[0]).toEqual({ refId: 'A', expr: 'A', datasource: { type: 'grafana-testdata', uid: 'abc', }, }); expect(result.datasource).toEqual({ type: 'grafana-testdata', uid: 'abc', }); }); it('Given panel with shared query', () => { const panel = buildGridItemFromPanelSchema({ datasource: { type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }, targets: [ { refId: 'A', panelId: 1, datasource: { type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }, }, ], }); const result = gridItemToPanel(panel); expect(result.targets?.length).toBe(1); expect(result.targets?.[0]).toEqual({ refId: 'A', panelId: 1, datasource: { type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }, }); expect(result.datasource).toEqual({ type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }); }); it('Given panel with shared query and transformations', () => { const panel = buildGridItemFromPanelSchema({ datasource: { type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }, targets: [ { refId: 'A', panelId: 1, datasource: { type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }, }, ], transformations: [ { id: 'reduce', options: { reducers: ['max'], mode: 'reduceFields', includeTimeField: false, }, }, ], }); const result = gridItemToPanel(panel); expect(result.transformations?.length).toBe(1); expect(result.targets?.length).toBe(1); expect(result.targets?.[0]).toEqual({ refId: 'A', panelId: 1, datasource: { type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }, }); expect(result.datasource).toEqual({ type: 'datasource', uid: SHARED_DASHBOARD_QUERY, }); }); it('Given panel with query caching options', () => { const panel = buildGridItemFromPanelSchema({ datasource: { type: 'grafana-testdata', uid: 'abc', }, cacheTimeout: '10', queryCachingTTL: 200000, maxDataPoints: 100, targets: [ { refId: 'A', expr: 'A', datasource: { type: 'grafana-testdata', uid: 'abc', }, }, { refId: 'B', expr: 'B', }, ], }); const result = gridItemToPanel(panel); expect(result.cacheTimeout).toBe('10'); expect(result.queryCachingTTL).toBe(200000); }); }); describe('Snapshots', () => { const fakeCurrentDate = dateTime('2023-01-01T20:00:00.000Z').toDate(); beforeEach(() => { advanceTo(fakeCurrentDate); }); it('attaches snapshot data to panels using Grafana snapshot query', async () => { const scene = transformSaveModelToScene({ dashboard: snapshotableDashboardJson as DashboardDataDTO, meta: {} }); activateFullSceneTree(scene); await new Promise((r) => setTimeout(r, 1)); const snapshot = transformSceneToSaveModel(scene, true); expect(snapshot.panels?.length).toBe(3); // Regular panel with SceneQueryRunner expect(snapshot.panels?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[0].targets?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[0].targets?.[0].snapshot[0].data).toEqual({ values: [ [100, 200, 300], [1, 2, 3], ], }); // Panel with transformations expect(snapshot.panels?.[1].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[1].targets?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[1].targets?.[0].snapshot[0].data).toEqual({ values: [ [100, 200, 300], [10, 20, 30], ], }); // @ts-expect-error expect(snapshot.panels?.[1].transformations).toEqual([ { id: 'reduce', options: {}, }, ]); // Panel with a shared query (dahsboard query) expect(snapshot.panels?.[2].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[2].targets?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[2].targets?.[0].snapshot[0].data).toEqual({ values: [ [100, 200, 300], [1, 2, 3], ], }); }); it('handles basic rows', async () => { const scene = transformSaveModelToScene({ dashboard: snapshotableWithRowsDashboardJson as DashboardDataDTO, meta: {}, }); activateFullSceneTree(scene); await new Promise((r) => setTimeout(r, 1)); const snapshot = transformSceneToSaveModel(scene, true); expect(snapshot.panels?.length).toBe(5); // @ts-expect-error expect(snapshot.panels?.[0].targets?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[0].targets?.[0].snapshot[0].data).toEqual({ values: [ [100, 200, 300], [1, 2, 3], ], }); // @ts-expect-error expect(snapshot.panels?.[1].targets).toBeUndefined(); // @ts-expect-error expect(snapshot.panels?.[1].panels).toEqual([]); // @ts-expect-error expect(snapshot.panels?.[1].collapsed).toEqual(false); // @ts-expect-error expect(snapshot.panels?.[2].targets?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[2].targets?.[0].snapshot[0].data).toEqual({ values: [ [100, 200, 300], [10, 20, 30], ], }); // @ts-expect-error expect(snapshot.panels?.[3].targets?.[0].datasource).toEqual(GRAFANA_DATASOURCE_REF); // @ts-expect-error expect(snapshot.panels?.[3].targets?.[0].snapshot[0].data).toEqual({ values: [ [1000, 2000, 3000], [100, 200, 300], ], }); // @ts-expect-error expect(snapshot.panels?.[4].targets).toBeUndefined(); // @ts-expect-error expect(snapshot.panels?.[4].panels).toHaveLength(1); // @ts-expect-error expect(snapshot.panels?.[4].collapsed).toEqual(true); }); describe('repeats', () => { it('handles repeated panels', async () => { const { scene, repeater } = buildPanelRepeaterScene({ variableQueryTime: 0, numberOfOptions: 2 }); activateFullSceneTree(scene); expect(repeater.state.repeatedPanels?.length).toBe(2); const result = panelRepeaterToPanels(repeater, true); expect(result).toHaveLength(2); // @ts-expect-error expect(result[0].scopedVars).toEqual({ server: { text: 'A', value: '1', }, }); // @ts-expect-error expect(result[1].scopedVars).toEqual({ server: { text: 'B', value: '2', }, }); expect(result[0].title).toEqual('Panel $server'); expect(result[1].title).toEqual('Panel $server'); }); it('handles repeated library panels', () => { const { scene, repeater } = buildPanelRepeaterScene( { variableQueryTime: 0, numberOfOptions: 2 }, new VizPanel({ key: 'panel-4', title: 'Panel blahh blah', fieldConfig: { defaults: {}, overrides: [], }, options: { legend: { calcs: [], displayMode: 'list', placement: 'bottom', showLegend: true, }, tooltip: { maxHeight: 600, mode: 'single', sort: 'none', }, }, $behaviors: [ new LibraryPanelBehavior({ name: 'Some lib panel panel', title: 'A panel', uid: 'lib-panel-uid', }), ], }) ); activateFullSceneTree(scene); const result = panelRepeaterToPanels(repeater, true); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ id: 4, title: 'A panel', libraryPanel: { name: 'Some lib panel panel', uid: 'lib-panel-uid', }, }); }); it('handles row repeats ', () => { const { scene, row } = buildPanelRepeaterScene({ variableQueryTime: 0, numberOfOptions: 2, useRowRepeater: true, usePanelRepeater: false, }); activateFullSceneTree(scene); let panels: Panel[] = []; gridRowToSaveModel(row, panels, true); expect(panels).toHaveLength(2); expect(panels[0].repeat).toBe('handler'); // @ts-expect-error expect(panels[0].scopedVars).toEqual({ handler: { text: 'AA', value: '11', }, }); expect(panels[1].title).toEqual('Panel $server'); expect(panels[1].gridPos).toEqual({ x: 0, y: 0, w: 10, h: 10 }); }); it('handles row repeats with panel repeater', () => { const { scene, row } = buildPanelRepeaterScene({ variableQueryTime: 0, numberOfOptions: 2, useRowRepeater: true, usePanelRepeater: true, }); activateFullSceneTree(scene); let panels: Panel[] = []; gridRowToSaveModel(row, panels, true); expect(panels[0].repeat).toBe('handler'); // @ts-expect-error expect(panels[0].scopedVars).toEqual({ handler: { text: 'AA', value: '11', }, }); // @ts-expect-error expect(panels[1].scopedVars).toEqual({ server: { text: 'A', value: '1', }, }); // @ts-expect-error expect(panels[2].scopedVars).toEqual({ server: { text: 'B', value: '2', }, }); expect(panels[1].title).toEqual('Panel $server'); expect(panels[2].title).toEqual('Panel $server'); }); }); describe('trimDashboardForSnapshot', () => { let snapshot: Dashboard = {} as Dashboard; beforeEach(() => { const scene = transformSaveModelToScene({ dashboard: snapshotableDashboardJson as DashboardDataDTO, meta: {} }); activateFullSceneTree(scene); snapshot = transformSceneToSaveModel(scene, true); }); it('should not mutate provided dashboard', () => { const result = trimDashboardForSnapshot('Snap title', getTimeRange({ from: 'now-6h', to: 'now' }), snapshot); expect(result).not.toBe(snapshot); }); it('should apply provided title and absolute time range', async () => { const result = trimDashboardForSnapshot('Snap title', getTimeRange({ from: 'now-6h', to: 'now' }), snapshot); expect(result.title).toBe('Snap title'); expect(result.time).toBeDefined(); expect(result.time!.from).toEqual('2023-01-01T14:00:00.000Z'); expect(result.time!.to).toEqual('2023-01-01T20:00:00.000Z'); }); it('should remove queries from annotations and attach empty snapshotData', () => { expect(snapshot.annotations?.list?.[0].target).toBeDefined(); expect(snapshot.annotations?.list?.[1].target).toBeDefined(); const result = trimDashboardForSnapshot('Snap title', getTimeRange({ from: 'now-6h', to: 'now' }), snapshot); expect(result.annotations?.list?.length).toBe(2); expect(result.annotations?.list?.[0].target).toBeUndefined(); expect(result.annotations?.list?.[0].snapshotData).toEqual([]); expect(result.annotations?.list?.[1].target).toBeUndefined(); expect(result.annotations?.list?.[1].snapshotData).toEqual([]); }); it('should remove queries from variables', () => { expect(snapshot.templating?.list?.length).toBe(1); const result = trimDashboardForSnapshot('Snap title', getTimeRange({ from: 'now-6h', to: 'now' }), snapshot); expect(result.templating?.list?.length).toBe(1); expect(result.templating?.list?.[0].query).toBe(''); expect(result.templating?.list?.[0].refresh).toBe(VariableRefresh.never); expect(result.templating?.list?.[0].options).toHaveLength(1); expect(result.templating?.list?.[0].options?.[0]).toEqual({ text: 'annotations', value: 'annotations', }); }); it('should snapshot a single panel when provided', () => { const vizPanel = new VizPanel({ key: getVizPanelKeyForPanelId(2), }); const result = trimDashboardForSnapshot( 'Snap title', getTimeRange({ from: 'now-6h', to: 'now' }), snapshot, vizPanel ); expect(snapshot.panels?.length).toBe(3); expect(result.panels?.length).toBe(1); expect(result.panels?.[0].gridPos).toEqual({ w: 24, x: 0, y: 0, h: 20 }); }); it('should remove links', async () => { const scene = transformSaveModelToScene({ dashboard: snapshotableDashboardJson as DashboardDataDTO, meta: {} }); activateFullSceneTree(scene); const snapshot = transformSceneToSaveModel(scene, true); expect(snapshot.links?.length).toBe(1); const result = trimDashboardForSnapshot('Snap title', getTimeRange({ from: 'now-6h', to: 'now' }), snapshot); expect(result.links?.length).toBe(0); }); }); }); describe('Given a scene with repeated panels and non-repeated panels', () => { it('should save repeated panels itemHeight as height', () => { const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO, meta: {}, }); const gridItem = sceneGraph.findByKey(scene, 'grid-item-2') as DashboardGridItem; expect(gridItem).toBeInstanceOf(DashboardGridItem); expect(gridItem.state.height).toBe(10); expect(gridItem.state.itemHeight).toBe(10); expect(gridItem.state.itemHeight).toBe(10); expect(gridItem.state.variableName).toBe('pod'); gridItem.setState({ itemHeight: 24 }); const saveModel = transformSceneToSaveModel(scene); expect(saveModel.panels?.[3].gridPos?.h).toBe(24); }); it('should not save non-repeated panels itemHeight as height', () => { const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO, meta: {}, }); const gridItem = sceneGraph.findByKey(scene, 'grid-item-15') as DashboardGridItem; expect(gridItem).toBeInstanceOf(DashboardGridItem); expect(gridItem.state.height).toBe(2); expect(gridItem.state.itemHeight).toBe(2); expect(gridItem.state.variableName).toBeUndefined(); gridItem.setState({ itemHeight: 24 }); let saveModel = transformSceneToSaveModel(scene); expect(saveModel.panels?.[1].gridPos?.h).toBe(2); gridItem.setState({ height: 34 }); saveModel = transformSceneToSaveModel(scene); expect(saveModel.panels?.[1].gridPos?.h).toBe(34); }); }); }); export function buildGridItemFromPanelSchema(panel: Partial) { return buildGridItemForPanel(new PanelModel(panel)); }