diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 77f9ce30b7f..73324d76280 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -37,6 +37,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -175,6 +176,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -283,6 +285,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -413,6 +416,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -551,6 +555,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -659,6 +664,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -771,6 +777,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { diff --git a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap index 2556cd86e4f..d2704b9d6ec 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -84,6 +84,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -355,6 +356,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -626,6 +628,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { @@ -897,6 +900,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "getVariablesFromState": [Function], "gnetId": null, "graphTooltip": 0, + "hasChangesThatAffectsAllPanels": false, "id": null, "links": Array [], "meta": Object { diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index ae16f89c207..c6485d6debc 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -6,6 +6,7 @@ import { variableAdapters } from '../../variables/adapters'; import { createAdHocVariableAdapter } from '../../variables/adhoc/adapter'; import { createQueryVariableAdapter } from '../../variables/query/adapter'; import { createCustomVariableAdapter } from '../../variables/custom/adapter'; +import { expect } from '../../../../test/lib/common'; jest.mock('app/core/services/context_srv', () => ({})); variableAdapters.setInit(() => [ @@ -764,3 +765,123 @@ describe('DashboardModel', () => { ); }); }); + +describe('exitViewPanel', () => { + function getTestContext() { + const panel: any = { setIsViewing: jest.fn() }; + const dashboard = new DashboardModel({}); + dashboard.startRefresh = jest.fn(); + dashboard.panelInView = panel; + + return { dashboard, panel }; + } + + describe('when called', () => { + it('then panelInView is set to undefined', () => { + const { dashboard, panel } = getTestContext(); + + dashboard.exitViewPanel(panel); + + expect(dashboard.panelInView).toBeUndefined(); + }); + + it('then setIsViewing is called on panel', () => { + const { dashboard, panel } = getTestContext(); + + dashboard.exitViewPanel(panel); + + expect(panel.setIsViewing).toHaveBeenCalledWith(false); + }); + + it('then startRefresh is not called', () => { + const { dashboard, panel } = getTestContext(); + + dashboard.exitViewPanel(panel); + + expect(dashboard.startRefresh).not.toHaveBeenCalled(); + }); + + describe('and there is a change that affects all panels', () => { + it('then startRefresh is not called', () => { + const { dashboard, panel } = getTestContext(); + dashboard.setChangeAffectsAllPanels(); + + dashboard.exitViewPanel(panel); + + expect(dashboard.startRefresh).toHaveBeenCalled(); + }); + }); + }); +}); + +describe('exitPanelEditor', () => { + function getTestContext() { + const panel: any = { destroy: jest.fn() }; + const dashboard = new DashboardModel({}); + dashboard.startRefresh = jest.fn(); + dashboard.panelInEdit = panel; + + return { dashboard, panel }; + } + + describe('when called', () => { + it('then panelInEdit is set to undefined', () => { + const { dashboard } = getTestContext(); + + dashboard.exitPanelEditor(); + + expect(dashboard.panelInEdit).toBeUndefined(); + }); + + it('then destroy is called on panel', () => { + const { dashboard, panel } = getTestContext(); + + dashboard.exitPanelEditor(); + + expect(panel.destroy).toHaveBeenCalled(); + }); + + it('then startRefresh is not called', () => { + const { dashboard } = getTestContext(); + + dashboard.exitPanelEditor(); + + expect(dashboard.startRefresh).not.toHaveBeenCalled(); + }); + + describe('and there is a change that affects all panels', () => { + it('then startRefresh is not called', () => { + const { dashboard } = getTestContext(); + dashboard.setChangeAffectsAllPanels(); + + dashboard.exitPanelEditor(); + + expect(dashboard.startRefresh).toHaveBeenCalled(); + }); + }); + }); +}); + +describe('setChangeAffectsAllPanels', () => { + it.each` + panelInEdit | panelInView | expected + ${null} | ${null} | ${false} + ${undefined} | ${undefined} | ${false} + ${null} | ${{}} | ${true} + ${undefined} | ${{}} | ${true} + ${{}} | ${null} | ${true} + ${{}} | ${undefined} | ${true} + ${{}} | ${{}} | ${true} + `( + 'when called and panelInEdit:{$panelInEdit} and panelInView:{$panelInView}', + ({ panelInEdit, panelInView, expected }) => { + const dashboard = new DashboardModel({}); + dashboard.panelInEdit = panelInEdit; + dashboard.panelInView = panelInView; + + dashboard.setChangeAffectsAllPanels(); + + expect(dashboard['hasChangesThatAffectsAllPanels']).toEqual(expected); + } + ); +}); diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 6a84c829f03..90633f79aa9 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -92,6 +92,7 @@ export class DashboardModel { panels: PanelModel[]; panelInEdit?: PanelModel; panelInView?: PanelModel; + private hasChangesThatAffectsAllPanels: boolean; // ------------------ // not persisted @@ -114,6 +115,7 @@ export class DashboardModel { panelInView: true, getVariablesFromState: true, formatDate: true, + hasChangesThatAffectsAllPanels: true, }; constructor(data: any, meta?: DashboardMeta, private getVariablesFromState: GetVariables = getVariables) { @@ -154,6 +156,7 @@ export class DashboardModel { this.addBuiltInAnnotationQuery(); this.sortPanelsByGridPos(); + this.hasChangesThatAffectsAllPanels = false; } addBuiltInAnnotationQuery() { @@ -365,11 +368,28 @@ export class DashboardModel { exitViewPanel(panel: PanelModel) { this.panelInView = undefined; panel.setIsViewing(false); + this.refreshIfChangeAffectsAllPanels(); } exitPanelEditor() { this.panelInEdit!.destroy(); this.panelInEdit = undefined; + this.refreshIfChangeAffectsAllPanels(); + } + + setChangeAffectsAllPanels() { + if (this.panelInEdit || this.panelInView) { + this.hasChangesThatAffectsAllPanels = true; + } + } + + private refreshIfChangeAffectsAllPanels() { + if (!this.hasChangesThatAffectsAllPanels) { + return; + } + + this.hasChangesThatAffectsAllPanels = false; + this.startRefresh(); } private ensureListExist(data: any) { diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 1d5de038ae2..d4d7dbd37e7 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -111,7 +111,7 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult => { - return (dispatch, getState) => { + return (dispatch) => { const dashboardModel: DashboardVariableModel = { ...initialVariableModelState, id: '__dashboard', @@ -493,6 +493,7 @@ export const variableUpdated = ( return Promise.all(promises).then(() => { if (emitChangeEvents) { const dashboard = getState().dashboard.getModel(); + dashboard?.setChangeAffectsAllPanels(); dashboard?.processRepeats(); locationService.partial(getQueryWithVariables(getState)); dashboard?.startRefresh(); @@ -526,6 +527,7 @@ export const onTimeRangeUpdated = ( try { await Promise.all(promises); const dashboard = getState().dashboard.getModel(); + dashboard?.setChangeAffectsAllPanels(); dashboard?.startRefresh(); } catch (error) { console.error(error); diff --git a/public/app/features/variables/state/onTimeRangeUpdated.test.ts b/public/app/features/variables/state/onTimeRangeUpdated.test.ts index bcd8b4a0c43..eda8183399a 100644 --- a/public/app/features/variables/state/onTimeRangeUpdated.test.ts +++ b/public/app/features/variables/state/onTimeRangeUpdated.test.ts @@ -54,11 +54,13 @@ const getTestContext = () => { const templateSrvMock = ({ updateTimeRange: updateTimeRangeMock } as unknown) as TemplateSrv; const dependencies: OnTimeRangeUpdatedDependencies = { templateSrv: templateSrvMock }; const templateVariableValueUpdatedMock = jest.fn(); + const setChangeAffectsAllPanelsMock = jest.fn(); const dashboard = ({ getModel: () => (({ templateVariableValueUpdated: templateVariableValueUpdatedMock, startRefresh: startRefreshMock, + setChangeAffectsAllPanels: setChangeAffectsAllPanelsMock, } as unknown) as DashboardModel), } as unknown) as DashboardState; const startRefreshMock = jest.fn(); @@ -82,6 +84,7 @@ const getTestContext = () => { updateTimeRangeMock, templateVariableValueUpdatedMock, startRefreshMock, + setChangeAffectsAllPanelsMock, }; }; @@ -95,6 +98,7 @@ describe('when onTimeRangeUpdated is dispatched', () => { updateTimeRangeMock, templateVariableValueUpdatedMock, startRefreshMock, + setChangeAffectsAllPanelsMock, } = getTestContext(); const tester = await reduxTester({ preloadedState }) @@ -117,6 +121,7 @@ describe('when onTimeRangeUpdated is dispatched', () => { expect(updateTimeRangeMock).toHaveBeenCalledWith(range); expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(1); expect(startRefreshMock).toHaveBeenCalledTimes(1); + expect(setChangeAffectsAllPanelsMock).toHaveBeenCalledTimes(1); }); }); @@ -130,6 +135,7 @@ describe('when onTimeRangeUpdated is dispatched', () => { updateTimeRangeMock, templateVariableValueUpdatedMock, startRefreshMock, + setChangeAffectsAllPanelsMock, } = getTestContext(); const base = await reduxTester({ preloadedState }) @@ -154,6 +160,7 @@ describe('when onTimeRangeUpdated is dispatched', () => { expect(updateTimeRangeMock).toHaveBeenCalledWith(range); expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(0); expect(startRefreshMock).toHaveBeenCalledTimes(1); + expect(setChangeAffectsAllPanelsMock).toHaveBeenCalledTimes(1); }); }); @@ -168,6 +175,7 @@ describe('when onTimeRangeUpdated is dispatched', () => { updateTimeRangeMock, templateVariableValueUpdatedMock, startRefreshMock, + setChangeAffectsAllPanelsMock, } = getTestContext(); adapter.updateOptions = jest.fn().mockRejectedValue(new Error('Something broke')); @@ -196,6 +204,7 @@ describe('when onTimeRangeUpdated is dispatched', () => { expect(updateTimeRangeMock).toHaveBeenCalledWith(range); expect(templateVariableValueUpdatedMock).toHaveBeenCalledTimes(0); expect(startRefreshMock).toHaveBeenCalledTimes(0); + expect(setChangeAffectsAllPanelsMock).toHaveBeenCalledTimes(0); }); }); });