diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index 17f240e4086..d046a958290 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -511,6 +511,57 @@ describe('DashboardModel', () => { expect(saveModel.time.from).toBe('now-3h'); expect(saveModel.time.to).toBe('now-1h'); }); + + it('getSaveModelClone should remove repeated panels and scopedVars', () => { + const dashboardJSON = { + panels: [ + { id: 1, type: 'row', repeat: 'dc', gridPos: { x: 0, y: 0, h: 1, w: 24 } }, + { id: 2, repeat: 'app', repeatDirection: 'h', gridPos: { x: 0, y: 1, h: 2, w: 8 } }, + ], + templating: { + list: [ + { + name: 'dc', + current: { + text: 'dc1 + dc2', + value: ['dc1', 'dc2'], + }, + options: [ + { text: 'dc1', value: 'dc1', selected: true }, + { text: 'dc2', value: 'dc2', selected: true }, + ], + }, + { + name: 'app', + current: { + text: 'se1 + se2', + value: ['se1', 'se2'], + }, + options: [ + { text: 'se1', value: 'se1', selected: true }, + { text: 'se2', value: 'se2', selected: true }, + ], + }, + ], + }, + }; + + const model = getDashboardModel(dashboardJSON); + model.processRepeats(); + expect(model.panels.filter((x) => x.type === 'row')).toHaveLength(2); + expect(model.panels.filter((x) => x.type !== 'row')).toHaveLength(4); + expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.dc.value).toBe('dc1'); + expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app.value).toBe('se1'); + + const saveModel = model.getSaveModelClone(); + expect(saveModel.panels.length).toBe(2); + expect(saveModel.panels[0].scopedVars).toBe(undefined); + expect(saveModel.panels[1].scopedVars).toBe(undefined); + + model.collapseRows(); + const savedModelWithCollapsedRows: any = model.getSaveModelClone(); + expect(savedModelWithCollapsedRows.panels[0].panels.length).toBe(1); + }); }); describe('Given model with template variable of type query', () => { diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 6d606bbfe02..81b5a43a6c0 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -209,8 +209,33 @@ export class DashboardModel { } // get panel save models - copy.panels = this.panels - .filter((panel: PanelModel) => panel.type !== 'add-panel') + copy.panels = this.getPanelSaveModels(); + + // sort by keys + copy = sortByKeys(copy); + copy.getVariables = () => { + return copy.templating.list; + }; + + return copy; + } + + private getPanelSaveModels() { + return this.panels + .filter((panel: PanelModel) => { + if (panel.type === 'add-panel') { + return false; + } + // skip repeated panels in the saved model + if (panel.repeatPanelId) { + return false; + } + // skip repeated rows in the saved model + if (panel.repeatedByRow) { + return false; + } + return true; + }) .map((panel: PanelModel) => { // If we save while editing we should include the panel in edit mode instead of the // unmodified source panel @@ -222,15 +247,24 @@ export class DashboardModel { } return panel.getSaveModel(); + }) + .map((model: any) => { + // Clear any scopedVars from persisted mode. This cannot be part of getSaveModel as we need to be able to copy + // panel models with preserved scopedVars, for example when going into edit mode. + delete model.scopedVars; + + // Clear any repeated panels from collapsed rows + if (model.type === 'row' && model.panels && model.panels.length > 0) { + model.panels = model.panels + .filter((rowPanel: PanelModel) => !rowPanel.repeatPanelId) + .map((model: PanelModel) => { + delete model.scopedVars; + return model; + }); + } + + return model; }); - - // sort by keys - copy = sortByKeys(copy); - copy.getVariables = () => { - return copy.templating.list; - }; - - return copy; } private updateTemplatingSaveModelClone(