From 9e0d84f1cf7520dce7d2aac85bf65ccfa5820ee6 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Wed, 3 Mar 2021 14:16:54 +0000 Subject: [PATCH] Library Panels: Change unsaved change detection logic (#31477) * Library Panels: Change unsaved change detection logic Change logic from diffing panel models to setting dirty flag --- .../components/PanelEditor/PanelEditor.tsx | 3 +-- .../PanelEditor/PanelEditorQueries.tsx | 8 +----- .../components/PanelEditor/state/actions.ts | 21 ++++++++------- .../features/dashboard/state/PanelModel.ts | 26 +++++++++++++++++-- .../PanelLibraryOptionsGroup.tsx | 6 +++-- .../app/features/library-panels/state/api.ts | 5 ++++ public/app/features/library-panels/utils.ts | 1 + 7 files changed, 47 insertions(+), 23 deletions(-) diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index afe3961ec8b..a8085f1fc34 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -194,8 +194,7 @@ export class PanelEditorUnconnected extends PureComponent { }; onPanelConfigChanged = (configKey: keyof PanelModel, value: any) => { - // @ts-ignore - this.props.panel[configKey] = value; + this.props.panel.setProperty(configKey, value); this.props.panel.render(); this.forceUpdate(); }; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx index 310a6314b96..4cb52e2cbb1 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -52,13 +52,7 @@ export class PanelEditorQueries extends PureComponent { const newDataSourceName = options.dataSource.default ? null : options.dataSource.name!; const dataSourceChanged = newDataSourceName !== panel.datasource; - panel.datasource = newDataSourceName; - panel.targets = options.queries; - panel.timeFrom = options.timeRange?.from; - panel.timeShift = options.timeRange?.shift; - panel.hideTimeOverride = options.timeRange?.hide; - panel.interval = options.minInterval; - panel.maxDataPoints = options.maxDataPoints; + panel.updateQueries(options); if (dataSourceChanged) { // trigger queries when changing data source diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.ts index 571e0f56bb8..1dd97d7b05c 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.ts @@ -13,8 +13,6 @@ import { updateLocation } from 'app/core/actions'; import { cleanUpEditPanel, panelModelAndPluginReady } from '../../../state/reducers'; import store from 'app/core/store'; import pick from 'lodash/pick'; -import omit from 'lodash/omit'; -import isEqual from 'lodash/isEqual'; export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult { return (dispatch) => { @@ -43,9 +41,9 @@ export function updateSourcePanel(sourcePanel: PanelModel): ThunkResult { } export function exitPanelEditor(): ThunkResult { - return (dispatch, getStore) => { + return async (dispatch, getStore) => { const dashboard = getStore().dashboard.getModel(); - const { getPanel, getSourcePanel, shouldDiscardChanges } = getStore().panelEditor; + const { getPanel, shouldDiscardChanges } = getStore().panelEditor; const onConfirm = () => dispatch( updateLocation({ @@ -54,11 +52,14 @@ export function exitPanelEditor(): ThunkResult { }) ); - const modifiedPanel = getPanel(); - const modifiedSaveModel = modifiedPanel.getSaveModel(); - const initialSaveModel = getSourcePanel().getSaveModel(); - const panelChanged = !isEqual(omit(initialSaveModel, 'id'), omit(modifiedSaveModel, 'id')); - if (shouldDiscardChanges || !modifiedPanel.libraryPanel || !panelChanged) { + const panel = getPanel(); + + if (shouldDiscardChanges || !panel.libraryPanel) { + onConfirm(); + return; + } + + if (!panel.hasChanged) { onConfirm(); return; } @@ -66,7 +67,7 @@ export function exitPanelEditor(): ThunkResult { appEvents.emit(CoreEvents.showModalReact, { component: SaveLibraryPanelModal, props: { - panel: modifiedPanel, + panel, folderId: dashboard!.meta.folderId, isOpen: true, onConfirm, diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 4e0c1850211..1c18cff8c41 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -36,6 +36,7 @@ import { isStandardFieldProp, restoreCustomOverrideRules, } from './getPanelOptionsWithDefaults'; +import { QueryGroupOptions } from 'app/types'; import { PanelModelLibraryPanel } from '../../library-panels/types'; export interface GridPos { @@ -57,6 +58,7 @@ const notPersistedProperties: { [str: string]: boolean } = { queryRunner: true, replaceVariables: true, editSourceId: true, + hasChanged: true, }; // For angular panels we need to clean up properties when changing type @@ -157,6 +159,7 @@ export class PanelModel implements DataConfigSource { isViewing: boolean; isEditing: boolean; isInView: boolean; + hasChanged: boolean; hasRefreshed: boolean; events: EventBus; @@ -228,12 +231,14 @@ export class PanelModel implements DataConfigSource { updateOptions(options: object) { this.options = options; + this.hasChanged = true; this.events.publish(new PanelOptionsChangedEvent()); this.render(); } updateFieldConfig(config: FieldConfigSource) { this.fieldConfig = config; + this.hasChanged = true; this.events.publish(new PanelOptionsChangedEvent()); this.resendLastResult(); @@ -392,6 +397,7 @@ export class PanelModel implements DataConfigSource { // switch this.type = pluginId; this.plugin = newPlugin; + this.hasChanged = true; // For some reason I need to rebind replace variables here, otherwise the viz repeater does not work this.replaceVariables = this.replaceVariables.bind(this); @@ -402,20 +408,30 @@ export class PanelModel implements DataConfigSource { } } - updateQueries(queries: DataQuery[]) { + updateQueries(options: QueryGroupOptions) { + this.datasource = options.dataSource.default ? null : options.dataSource.name!; + this.timeFrom = options.timeRange?.from; + this.timeShift = options.timeRange?.shift; + this.hideTimeOverride = options.timeRange?.hide; + this.interval = options.minInterval; + this.maxDataPoints = options.maxDataPoints; + this.targets = options.queries; + this.hasChanged = true; + this.events.publish(new PanelQueriesChangedEvent()); - this.targets = queries; } addQuery(query?: Partial) { query = query || { refId: 'A' }; query.refId = getNextRefIdChar(this.targets); this.targets.push(query as DataQuery); + this.hasChanged = true; } changeQuery(query: DataQuery, index: number) { // ensure refId is maintained query.refId = this.targets[index].refId; + this.hasChanged = true; // update query in array this.targets = this.targets.map((item, itemIndex) => { @@ -486,9 +502,15 @@ export class PanelModel implements DataConfigSource { setTransformations(transformations: DataTransformerConfig[]) { this.transformations = transformations; this.resendLastResult(); + this.hasChanged = true; this.events.publish(new PanelTransformationsChangedEvent()); } + setProperty(key: keyof this, value: any) { + this[key] = value; + this.hasChanged = true; + } + replaceVariables(value: string, extraVars: ScopedVars | undefined, format?: string | Function) { let vars = this.scopedVars; diff --git a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx index e3ec7471f52..bc8489dd2d8 100644 --- a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx +++ b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx @@ -28,8 +28,10 @@ export const PanelLibraryOptionsGroup: React.FC = ({ panel, dashboard }) libraryPanel: toPanelModelLibraryPanel(panelInfo), }); - // dummy change for re-render - // onPanelConfigChange('isEditing', true); + // Though the panel model has changed, since we're switching to an existing + // library panel, we reset the "hasChanged" state. + panel.hasChanged = false; + panel.refresh(); panel.events.publish(PanelQueriesChangedEvent); }; diff --git a/public/app/features/library-panels/state/api.ts b/public/app/features/library-panels/state/api.ts index b52b3afd348..8794894ee57 100644 --- a/public/app/features/library-panels/state/api.ts +++ b/public/app/features/library-panels/state/api.ts @@ -6,6 +6,11 @@ export async function getLibraryPanels(): Promise { return result; } +export async function getLibraryPanel(uid: string): Promise { + const { result } = await getBackendSrv().get(`/api/library-panels/${uid}`); + return result; +} + export async function addLibraryPanel( panelSaveModel: PanelModelWithLibraryPanel, folderId: number diff --git a/public/app/features/library-panels/utils.ts b/public/app/features/library-panels/utils.ts index f3b97234f3c..5202c8f72a0 100644 --- a/public/app/features/library-panels/utils.ts +++ b/public/app/features/library-panels/utils.ts @@ -40,6 +40,7 @@ function toPanelSaveModel(panel: PanelModel): any { function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryPanelDTO): void { panel.restoreModel({ ...updated.model, + hasChanged: false, // reset dirty flag, since changes have been saved libraryPanel: toPanelModelLibraryPanel(updated), }); panel.refresh();