diff --git a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx index fc19f772d4f..7beaa7a7d2a 100644 --- a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx +++ b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx @@ -1,3 +1,5 @@ +import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; + import { setContextSrv } from '../../../../core/services/context_srv'; import { DashboardModel } from '../../state/DashboardModel'; import { PanelModel } from '../../state/PanelModel'; @@ -138,6 +140,17 @@ describe('DashboardPrompt', () => { }); }); + it('Should ignore panel schema migrations', () => { + const { original, dash } = getTestContext(); + const plugin = getPanelPlugin({}).setMigrationHandler((panel) => { + delete (panel as any).legend; + return { option1: 'Aasd' }; + }); + + dash.panels[0].pluginLoaded(plugin); + expect(hasChanges(dash, original)).toBe(false); + }); + describe('when called with fromFile', () => { it('then it should return true', () => { const { original, dash } = getTestContext(); diff --git a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx index f694e245afe..8e945ce9a8c 100644 --- a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx +++ b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx @@ -1,5 +1,5 @@ import * as H from 'history'; -import { each, filter, find } from 'lodash'; +import { each, find } from 'lodash'; import React, { useContext, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Prompt } from 'react-router-dom'; @@ -184,22 +184,7 @@ function cleanDashboardFromIgnoredChanges(dashData: any) { // ignore iteration property delete dash.iteration; - dash.panels = filter(dash.panels, (panel) => { - if (panel.repeatPanelId) { - return false; - } - - // remove scopedVars - panel.scopedVars = undefined; - - // ignore panel legend sort - if (panel.legend) { - delete panel.legend.sort; - delete panel.legend.sortDesc; - } - - return true; - }); + dash.panels = []; // ignore template variable values each(dash.getVariables(), (variable: any) => { @@ -212,6 +197,10 @@ function cleanDashboardFromIgnoredChanges(dashData: any) { } export function hasChanges(current: DashboardModel, original: any) { + if (current.hasUnsavedChanges()) { + return true; + } + const currentClean = cleanDashboardFromIgnoredChanges(current.getSaveModelClone()); const originalClean = cleanDashboardFromIgnoredChanges(original); diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx index f6ff78d44d2..58d7e9726b9 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx @@ -50,8 +50,8 @@ export class DashboardRow extends React.Component { }; onUpdate = (title: string, repeat?: string | null) => { - this.props.panel['title'] = title; - this.props.panel['repeat'] = repeat ?? undefined; + this.props.panel.setProperty('title', title); + this.props.panel.setProperty('repeat', repeat ?? undefined); this.props.panel.render(); this.props.dashboard.processRepeats(); this.forceUpdate(); diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index e5d8e613f20..7c0cdf48a22 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -122,7 +122,6 @@ export class DashboardGridUnconnected extends PureComponent { onResize: ItemCallback = (layout, oldItem, newItem) => { const panel = this.panelMap[newItem.i!]; panel.updateGridPos(newItem); - panel.configRev++; // trigger change handler }; onResizeStop: ItemCallback = (layout, oldItem, newItem) => { diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index 48ba907b1da..da4e8165a8e 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -367,6 +367,15 @@ describe('DashboardModel', () => { dashboard.toggleRow(dashboard.panels[1]); }); + it('should not impact hasUnsavedChanges', () => { + expect(dashboard.hasUnsavedChanges()).toBe(false); + }); + + it('should impact hasUnsavedChanges if panels have changes when row is collapsed', () => { + dashboard.panels[0].setProperty('title', 'new title'); + expect(dashboard.hasUnsavedChanges()).toBe(true); + }); + it('should remove panels and put them inside collapsed row', () => { expect(dashboard.panels.length).toBe(3); expect(dashboard.panels[1].panels?.length).toBe(2); diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 693efdcff95..29ca48162de 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -499,10 +499,6 @@ export class DashboardModel implements TimeModel { hasUnsavedChanges() { const changedPanel = this.panels.find((p) => p.hasChanged); - if (changedPanel) { - console.log('Panel has changed', changedPanel); - } - return Boolean(changedPanel); } @@ -875,6 +871,10 @@ export class DashboardModel implements TimeModel { row.panels = rowPanels.map((panel: PanelModel) => panel.getSaveModel()); row.collapsed = true; + if (rowPanels.some((panel) => panel.hasChanged)) { + row.configRev++; + } + // emit change event this.events.publish(new DashboardPanelsChangedEvent()); return; diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index df5459411aa..c58f9dc8f5d 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -435,6 +435,20 @@ describe('PanelModel', () => { }); }); + describe('updateGridPos', () => { + it('Should not cause configRev if no change', () => { + model.gridPos = { w: 1, h: 1, x: 1, y: 2 }; + model.updateGridPos({ w: 1, h: 1, x: 1, y: 2 }); + expect(model.hasChanged).toBe(false); + }); + + it('Should not cause configRev if gridPos is different', () => { + model.gridPos = { w: 1, h: 1, x: 1, y: 2 }; + model.updateGridPos({ w: 10, h: 1, x: 1, y: 2 }); + expect(model.hasChanged).toBe(true); + }); + }); + describe('destroy', () => { it('Should still preserve last query result', () => { model.getQueryRunner().useLastResultFrom({ diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index b336df4d53f..bf5d098184c 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -298,10 +298,20 @@ export class PanelModel implements DataConfigSource, IPanelModel { } updateGridPos(newPos: GridPos) { + if ( + newPos.x === this.gridPos.x && + newPos.y === this.gridPos.y && + newPos.h === this.gridPos.h && + newPos.w === this.gridPos.w + ) { + return; + } + this.gridPos.x = newPos.x; this.gridPos.y = newPos.y; this.gridPos.w = newPos.w; this.gridPos.h = newPos.h; + this.configRev++; } runAllPanelQueries(