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
pull/31646/head
kay delaney 4 years ago committed by GitHub
parent f5d1fa48ca
commit 9e0d84f1cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx
  2. 8
      public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx
  3. 21
      public/app/features/dashboard/components/PanelEditor/state/actions.ts
  4. 26
      public/app/features/dashboard/state/PanelModel.ts
  5. 6
      public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx
  6. 5
      public/app/features/library-panels/state/api.ts
  7. 1
      public/app/features/library-panels/utils.ts

@ -194,8 +194,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}; };
onPanelConfigChanged = (configKey: keyof PanelModel, value: any) => { onPanelConfigChanged = (configKey: keyof PanelModel, value: any) => {
// @ts-ignore this.props.panel.setProperty(configKey, value);
this.props.panel[configKey] = value;
this.props.panel.render(); this.props.panel.render();
this.forceUpdate(); this.forceUpdate();
}; };

@ -52,13 +52,7 @@ export class PanelEditorQueries extends PureComponent<Props, State> {
const newDataSourceName = options.dataSource.default ? null : options.dataSource.name!; const newDataSourceName = options.dataSource.default ? null : options.dataSource.name!;
const dataSourceChanged = newDataSourceName !== panel.datasource; const dataSourceChanged = newDataSourceName !== panel.datasource;
panel.datasource = newDataSourceName; panel.updateQueries(options);
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;
if (dataSourceChanged) { if (dataSourceChanged) {
// trigger queries when changing data source // trigger queries when changing data source

@ -13,8 +13,6 @@ import { updateLocation } from 'app/core/actions';
import { cleanUpEditPanel, panelModelAndPluginReady } from '../../../state/reducers'; import { cleanUpEditPanel, panelModelAndPluginReady } from '../../../state/reducers';
import store from 'app/core/store'; import store from 'app/core/store';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import omit from 'lodash/omit';
import isEqual from 'lodash/isEqual';
export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult<void> { export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult<void> {
return (dispatch) => { return (dispatch) => {
@ -43,9 +41,9 @@ export function updateSourcePanel(sourcePanel: PanelModel): ThunkResult<void> {
} }
export function exitPanelEditor(): ThunkResult<void> { export function exitPanelEditor(): ThunkResult<void> {
return (dispatch, getStore) => { return async (dispatch, getStore) => {
const dashboard = getStore().dashboard.getModel(); const dashboard = getStore().dashboard.getModel();
const { getPanel, getSourcePanel, shouldDiscardChanges } = getStore().panelEditor; const { getPanel, shouldDiscardChanges } = getStore().panelEditor;
const onConfirm = () => const onConfirm = () =>
dispatch( dispatch(
updateLocation({ updateLocation({
@ -54,11 +52,14 @@ export function exitPanelEditor(): ThunkResult<void> {
}) })
); );
const modifiedPanel = getPanel(); const panel = getPanel();
const modifiedSaveModel = modifiedPanel.getSaveModel();
const initialSaveModel = getSourcePanel().getSaveModel(); if (shouldDiscardChanges || !panel.libraryPanel) {
const panelChanged = !isEqual(omit(initialSaveModel, 'id'), omit(modifiedSaveModel, 'id')); onConfirm();
if (shouldDiscardChanges || !modifiedPanel.libraryPanel || !panelChanged) { return;
}
if (!panel.hasChanged) {
onConfirm(); onConfirm();
return; return;
} }
@ -66,7 +67,7 @@ export function exitPanelEditor(): ThunkResult<void> {
appEvents.emit(CoreEvents.showModalReact, { appEvents.emit(CoreEvents.showModalReact, {
component: SaveLibraryPanelModal, component: SaveLibraryPanelModal,
props: { props: {
panel: modifiedPanel, panel,
folderId: dashboard!.meta.folderId, folderId: dashboard!.meta.folderId,
isOpen: true, isOpen: true,
onConfirm, onConfirm,

@ -36,6 +36,7 @@ import {
isStandardFieldProp, isStandardFieldProp,
restoreCustomOverrideRules, restoreCustomOverrideRules,
} from './getPanelOptionsWithDefaults'; } from './getPanelOptionsWithDefaults';
import { QueryGroupOptions } from 'app/types';
import { PanelModelLibraryPanel } from '../../library-panels/types'; import { PanelModelLibraryPanel } from '../../library-panels/types';
export interface GridPos { export interface GridPos {
@ -57,6 +58,7 @@ const notPersistedProperties: { [str: string]: boolean } = {
queryRunner: true, queryRunner: true,
replaceVariables: true, replaceVariables: true,
editSourceId: true, editSourceId: true,
hasChanged: true,
}; };
// For angular panels we need to clean up properties when changing type // For angular panels we need to clean up properties when changing type
@ -157,6 +159,7 @@ export class PanelModel implements DataConfigSource {
isViewing: boolean; isViewing: boolean;
isEditing: boolean; isEditing: boolean;
isInView: boolean; isInView: boolean;
hasChanged: boolean;
hasRefreshed: boolean; hasRefreshed: boolean;
events: EventBus; events: EventBus;
@ -228,12 +231,14 @@ export class PanelModel implements DataConfigSource {
updateOptions(options: object) { updateOptions(options: object) {
this.options = options; this.options = options;
this.hasChanged = true;
this.events.publish(new PanelOptionsChangedEvent()); this.events.publish(new PanelOptionsChangedEvent());
this.render(); this.render();
} }
updateFieldConfig(config: FieldConfigSource) { updateFieldConfig(config: FieldConfigSource) {
this.fieldConfig = config; this.fieldConfig = config;
this.hasChanged = true;
this.events.publish(new PanelOptionsChangedEvent()); this.events.publish(new PanelOptionsChangedEvent());
this.resendLastResult(); this.resendLastResult();
@ -392,6 +397,7 @@ export class PanelModel implements DataConfigSource {
// switch // switch
this.type = pluginId; this.type = pluginId;
this.plugin = newPlugin; this.plugin = newPlugin;
this.hasChanged = true;
// For some reason I need to rebind replace variables here, otherwise the viz repeater does not work // For some reason I need to rebind replace variables here, otherwise the viz repeater does not work
this.replaceVariables = this.replaceVariables.bind(this); 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.events.publish(new PanelQueriesChangedEvent());
this.targets = queries;
} }
addQuery(query?: Partial<DataQuery>) { addQuery(query?: Partial<DataQuery>) {
query = query || { refId: 'A' }; query = query || { refId: 'A' };
query.refId = getNextRefIdChar(this.targets); query.refId = getNextRefIdChar(this.targets);
this.targets.push(query as DataQuery); this.targets.push(query as DataQuery);
this.hasChanged = true;
} }
changeQuery(query: DataQuery, index: number) { changeQuery(query: DataQuery, index: number) {
// ensure refId is maintained // ensure refId is maintained
query.refId = this.targets[index].refId; query.refId = this.targets[index].refId;
this.hasChanged = true;
// update query in array // update query in array
this.targets = this.targets.map((item, itemIndex) => { this.targets = this.targets.map((item, itemIndex) => {
@ -486,9 +502,15 @@ export class PanelModel implements DataConfigSource {
setTransformations(transformations: DataTransformerConfig[]) { setTransformations(transformations: DataTransformerConfig[]) {
this.transformations = transformations; this.transformations = transformations;
this.resendLastResult(); this.resendLastResult();
this.hasChanged = true;
this.events.publish(new PanelTransformationsChangedEvent()); 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) { replaceVariables(value: string, extraVars: ScopedVars | undefined, format?: string | Function) {
let vars = this.scopedVars; let vars = this.scopedVars;

@ -28,8 +28,10 @@ export const PanelLibraryOptionsGroup: React.FC<Props> = ({ panel, dashboard })
libraryPanel: toPanelModelLibraryPanel(panelInfo), libraryPanel: toPanelModelLibraryPanel(panelInfo),
}); });
// dummy change for re-render // Though the panel model has changed, since we're switching to an existing
// onPanelConfigChange('isEditing', true); // library panel, we reset the "hasChanged" state.
panel.hasChanged = false;
panel.refresh(); panel.refresh();
panel.events.publish(PanelQueriesChangedEvent); panel.events.publish(PanelQueriesChangedEvent);
}; };

@ -6,6 +6,11 @@ export async function getLibraryPanels(): Promise<LibraryPanelDTO[]> {
return result; return result;
} }
export async function getLibraryPanel(uid: string): Promise<LibraryPanelDTO> {
const { result } = await getBackendSrv().get(`/api/library-panels/${uid}`);
return result;
}
export async function addLibraryPanel( export async function addLibraryPanel(
panelSaveModel: PanelModelWithLibraryPanel, panelSaveModel: PanelModelWithLibraryPanel,
folderId: number folderId: number

@ -40,6 +40,7 @@ function toPanelSaveModel(panel: PanelModel): any {
function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryPanelDTO): void { function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryPanelDTO): void {
panel.restoreModel({ panel.restoreModel({
...updated.model, ...updated.model,
hasChanged: false, // reset dirty flag, since changes have been saved
libraryPanel: toPanelModelLibraryPanel(updated), libraryPanel: toPanelModelLibraryPanel(updated),
}); });
panel.refresh(); panel.refresh();

Loading…
Cancel
Save