diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts index 8669f5c9dda..d142187c601 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts @@ -1,6 +1,6 @@ import { thunkTester } from '../../../../../../test/core/thunk/thunkTester'; import { closeEditor, initialState, PanelEditorState } from './reducers'; -import { initPanelEditor, exitPanelEditor } from './actions'; +import { exitPanelEditor, initPanelEditor, skipPanelUpdate } from './actions'; import { cleanUpEditPanel, panelModelAndPluginReady } from '../../../state/reducers'; import { DashboardModel, PanelModel } from '../../../state'; import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; @@ -125,4 +125,46 @@ describe('panelEditor actions', () => { expect(sourcePanel.getOptions()).toEqual({}); }); }); + + describe('skipPanelUpdate', () => { + describe('when called with panel with an library uid different from the modified panel', () => { + it('then it should return true', () => { + const meta: any = {}; + const modified: any = { libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + const panel: any = { libraryPanel: { uid: '456', name: 'Name', meta, version: 1 } }; + + expect(skipPanelUpdate(modified, panel)).toEqual(true); + }); + }); + + describe('when called with a panel that is the same as the modified panel', () => { + it('then it should return true', () => { + const meta: any = {}; + const modified: any = { editSourceId: 14, libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + const panel: any = { id: 14, libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + + expect(skipPanelUpdate(modified, panel)).toEqual(true); + }); + }); + + describe('when called with a panel that is repeated', () => { + it('then it should return true', () => { + const meta: any = {}; + const modified: any = { libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + const panel: any = { repeatPanelId: 14, libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + + expect(skipPanelUpdate(modified, panel)).toEqual(true); + }); + }); + + describe('when called with a panel that is a duplicate of the modified panel', () => { + it('then it should return false', () => { + const meta: any = {}; + const modified: any = { libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + const panel: any = { libraryPanel: { uid: '123', name: 'Name', meta, version: 1 } }; + + expect(skipPanelUpdate(modified, panel)).toEqual(false); + }); + }); + }); }); diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.ts index 99373832015..9535e3c907e 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.ts @@ -4,9 +4,9 @@ import { closeEditor, PANEL_EDITOR_UI_STATE_STORAGE_KEY, PanelEditorUIState, + setDiscardChanges, setPanelEditorUIState, updateEditorInitState, - setDiscardChanges, } from './reducers'; import { cleanUpEditPanel, panelModelAndPluginReady } from '../../../state/reducers'; import store from 'app/core/store'; @@ -33,38 +33,68 @@ export function discardPanelChanges(): ThunkResult { }; } -function updateDuplicateLibraryPanels(modifiedPanel: PanelModel, dashboard: DashboardModel | null, dispatch: any) { - if (modifiedPanel.libraryPanel?.uid === undefined || !dashboard) { - return; - } - - const modifiedSaveModel = modifiedPanel.getSaveModel(); - for (const panel of dashboard.panels) { - if (panel.libraryPanel?.uid !== modifiedPanel.libraryPanel!.uid) { - continue; +export function updateDuplicateLibraryPanels( + modifiedPanel: PanelModel, + dashboard: DashboardModel | null +): ThunkResult { + return (dispatch) => { + if (modifiedPanel.libraryPanel?.uid === undefined || !dashboard) { + return; } - panel.restoreModel({ - ...modifiedSaveModel, - ...pick(panel, 'gridPos', 'id'), - }); + const modifiedSaveModel = modifiedPanel.getSaveModel(); + for (const panel of dashboard.panels) { + if (skipPanelUpdate(modifiedPanel, panel)) { + continue; + } - // Loaded plugin is not included in the persisted properties - // So is not handled by restoreModel - const pluginChanged = panel.plugin?.meta.id !== modifiedPanel.plugin?.meta.id; - panel.plugin = modifiedPanel.plugin; - panel.configRev++; + panel.restoreModel({ + ...modifiedSaveModel, + ...pick(panel, 'gridPos', 'id'), + }); - if (pluginChanged) { - dispatch(panelModelAndPluginReady({ panelId: panel.id, plugin: panel.plugin! })); + // Loaded plugin is not included in the persisted properties + // So is not handled by restoreModel + const pluginChanged = panel.plugin?.meta.id !== modifiedPanel.plugin?.meta.id; + panel.plugin = modifiedPanel.plugin; + panel.configRev++; + + if (pluginChanged) { + dispatch(panelModelAndPluginReady({ panelId: panel.id, plugin: panel.plugin! })); + } + + // Resend last query result on source panel query runner + // But do this after the panel edit editor exit process has completed + setTimeout(() => { + panel.getQueryRunner().useLastResultFrom(modifiedPanel.getQueryRunner()); + }, 20); } - // Resend last query result on source panel query runner - // But do this after the panel edit editor exit process has completed - setTimeout(() => { - panel.getQueryRunner().useLastResultFrom(modifiedPanel.getQueryRunner()); - }, 20); + if (modifiedPanel.repeat) { + // We skip any repeated library panels so we need to update them by calling processRepeats + // But do this after the panel edit editor exit process has completed + setTimeout(() => dashboard.processRepeats(), 20); + } + }; +} + +export function skipPanelUpdate(modifiedPanel: PanelModel, panelToUpdate: PanelModel): boolean { + // don't update library panels that aren't of the same type + if (panelToUpdate.libraryPanel?.uid !== modifiedPanel.libraryPanel!.uid) { + return true; } + + // don't update the modifiedPanel twice + if (panelToUpdate.id && panelToUpdate.id === modifiedPanel.editSourceId) { + return true; + } + + // don't update library panels that are repeated + if (panelToUpdate.repeatPanelId) { + return true; + } + + return false; } export function exitPanelEditor(): ThunkResult { @@ -78,7 +108,7 @@ export function exitPanelEditor(): ThunkResult { const sourcePanel = getSourcePanel(); const panelTypeChanged = sourcePanel.type !== panel.type; - updateDuplicateLibraryPanels(panel, dashboard, dispatch); + dispatch(updateDuplicateLibraryPanels(panel, dashboard)); // restore the source panel ID before we update source panel modifiedSaveModel.id = sourcePanel.id;