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
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user