From 1a96f3742f84284e64f48fb52f0ef6a78dad1ac0 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Fri, 6 Oct 2023 14:32:04 +0200 Subject: [PATCH] Dashboards: It always detect changes when saving an existing dashboard (#76116) Deep clone originalDashboard to avoid mutations by object reference --- .../dashboard/state/DashboardModel.test.ts | 25 ++++++++++++++++++- .../dashboard/state/DashboardModel.ts | 10 +++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index 620ee45a8c4..8d764aa671b 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -1,7 +1,7 @@ import { keys as _keys } from 'lodash'; import { dateTime, TimeRange, VariableHide } from '@grafana/data'; -import { defaultVariableModel } from '@grafana/schema'; +import { Dashboard, defaultVariableModel } from '@grafana/schema'; import { contextSrv } from 'app/core/services/context_srv'; import { getDashboardModel } from '../../../../test/helpers/getDashboardModel'; @@ -52,6 +52,29 @@ describe('DashboardModel', () => { }); }); + describe('when storing original dashboard data', () => { + let originalDashboard: Dashboard = { + editable: true, + graphTooltip: 0, + schemaVersion: 1, + timezone: '', + title: 'original.title', + }; + let model: DashboardModel; + + beforeEach(() => { + model = new DashboardModel(originalDashboard); + }); + + it('should be returned from getOriginalDashboard without modifications', () => { + expect(model.getOriginalDashboard()).toEqual(originalDashboard); + }); + + it('should return a copy of the provided object', () => { + expect(model.getOriginalDashboard()).not.toBe(originalDashboard); + }); + }); + describe('when getting next panel id', () => { let model: DashboardModel; diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 6204e48960a..f10718649dd 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -24,6 +24,7 @@ import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, REPEAT_DIR_VERT import { contextSrv } from 'app/core/services/context_srv'; import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; import { isAngularDatasourcePlugin } from 'app/features/plugins/angularDeprecation/utils'; +import { deepFreeze } from 'app/features/plugins/extensions/utils'; import { variableAdapters } from 'app/features/variables/adapters'; import { onTimeRangeUpdated } from 'app/features/variables/state/actions'; import { GetVariables, getVariablesByKey } from 'app/features/variables/state/selectors'; @@ -172,7 +173,8 @@ export class DashboardModel implements TimeModel { this.links = data.links ?? []; this.gnetId = data.gnetId || null; this.panels = map(data.panels ?? [], (panelData: any) => new PanelModel(panelData)); - this.originalDashboard = data; + // Deep clone original dashboard to avoid mutations by object reference + this.originalDashboard = cloneDeep(data); this.ensurePanelsHaveUniqueIds(); this.formatDate = this.formatDate.bind(this); @@ -1081,7 +1083,7 @@ export class DashboardModel implements TimeModel { } resetOriginalTime() { - this.originalTime = cloneDeep(this.time); + this.originalTime = deepFreeze(this.time); } hasTimeChanged() { @@ -1267,8 +1269,8 @@ export class DashboardModel implements TimeModel { return variables.map((variable) => ({ name: variable.name, type: variable.type, - current: cloneDeep(variable.current), - filters: cloneDeep(variable.filters), + current: deepFreeze(variable.current), + filters: deepFreeze(variable.filters), })); }