mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
* Performance: Standardize lodash imports to use destructured members Changes lodash imports of the form `import x from 'lodash/x'` to `import { x } from 'lodash'` to reduce bundle size. * Remove unnecessary _ import from Graph component * Enforce lodash import style * Fix remaining lodash imports
162 lines
4.6 KiB
TypeScript
162 lines
4.6 KiB
TypeScript
import { each, filter, find } from 'lodash';
|
|
import { DashboardModel } from '../state/DashboardModel';
|
|
import { contextSrv } from 'app/core/services/context_srv';
|
|
import { appEvents } from 'app/core/app_events';
|
|
import { UnsavedChangesModal } from '../components/SaveDashboard/UnsavedChangesModal';
|
|
import { DashboardSavedEvent, ShowModalReactEvent } from '../../../types/events';
|
|
import { locationService } from '@grafana/runtime';
|
|
import angular from 'angular';
|
|
|
|
export class ChangeTracker {
|
|
init(dashboard: DashboardModel, originalCopyDelay: number) {
|
|
let original: object | null = null;
|
|
let originalPath = locationService.getLocation().pathname;
|
|
|
|
// register events
|
|
const savedEventUnsub = appEvents.subscribe(DashboardSavedEvent, () => {
|
|
original = dashboard.getSaveModelClone();
|
|
originalPath = locationService.getLocation().pathname;
|
|
});
|
|
|
|
if (originalCopyDelay && !dashboard.meta.fromExplore) {
|
|
setTimeout(() => {
|
|
// wait for different services to patch the dashboard (missing properties)
|
|
original = dashboard.getSaveModelClone();
|
|
}, originalCopyDelay);
|
|
} else {
|
|
original = dashboard.getSaveModelClone();
|
|
}
|
|
|
|
const history = locationService.getHistory();
|
|
|
|
const blockUnsub = history.block((location) => {
|
|
if (originalPath === location.pathname) {
|
|
return;
|
|
}
|
|
|
|
if (this.ignoreChanges(dashboard, original)) {
|
|
return;
|
|
}
|
|
|
|
if (!this.hasChanges(dashboard, original!)) {
|
|
return;
|
|
}
|
|
|
|
appEvents.publish(
|
|
new ShowModalReactEvent({
|
|
component: UnsavedChangesModal,
|
|
props: {
|
|
dashboard: dashboard,
|
|
onSaveSuccess: () => {
|
|
original = dashboard.getSaveModelClone();
|
|
history.push(location);
|
|
},
|
|
onDiscard: () => {
|
|
original = dashboard.getSaveModelClone();
|
|
history.push(location);
|
|
},
|
|
},
|
|
})
|
|
);
|
|
|
|
return false;
|
|
});
|
|
|
|
const historyListenUnsub = history.listen((location) => {
|
|
if (originalPath !== location.pathname) {
|
|
blockUnsub();
|
|
historyListenUnsub();
|
|
savedEventUnsub.unsubscribe();
|
|
}
|
|
});
|
|
}
|
|
|
|
// for some dashboards and users
|
|
// changes should be ignored
|
|
ignoreChanges(current: DashboardModel, original: object | null) {
|
|
if (!original) {
|
|
return true;
|
|
}
|
|
|
|
// Ignore changes if the user has been signed out
|
|
if (!contextSrv.isSignedIn) {
|
|
return true;
|
|
}
|
|
|
|
if (!current || !current.meta) {
|
|
return true;
|
|
}
|
|
|
|
const { canSave, fromScript, fromFile } = current.meta;
|
|
if (!contextSrv.isEditor && !canSave) {
|
|
return true;
|
|
}
|
|
|
|
return !canSave || fromScript || fromFile;
|
|
}
|
|
|
|
// remove stuff that should not count in diff
|
|
cleanDashboardFromIgnoredChanges(dashData: any) {
|
|
// need to new up the domain model class to get access to expand / collapse row logic
|
|
const model = new DashboardModel(dashData);
|
|
|
|
// Expand all rows before making comparison. This is required because row expand / collapse
|
|
// change order of panel array and panel positions.
|
|
model.expandRows();
|
|
|
|
const dash = model.getSaveModelClone();
|
|
|
|
// ignore time and refresh
|
|
dash.time = 0;
|
|
dash.refresh = 0;
|
|
dash.schemaVersion = 0;
|
|
dash.timezone = 0;
|
|
|
|
// ignore iteration property
|
|
delete dash.iteration;
|
|
|
|
dash.panels = filter(dash.panels, (panel) => {
|
|
if (panel.repeatPanelId) {
|
|
return false;
|
|
}
|
|
|
|
// remove scopedVars
|
|
panel.scopedVars = undefined;
|
|
|
|
// ignore panel legend sort
|
|
if (panel.legend) {
|
|
delete panel.legend.sort;
|
|
delete panel.legend.sortDesc;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// ignore template variable values
|
|
each(dash.getVariables(), (variable: any) => {
|
|
variable.current = null;
|
|
variable.options = null;
|
|
variable.filters = null;
|
|
});
|
|
|
|
return dash;
|
|
}
|
|
|
|
hasChanges(current: DashboardModel, original: any) {
|
|
const currentClean = this.cleanDashboardFromIgnoredChanges(current.getSaveModelClone());
|
|
const originalClean = this.cleanDashboardFromIgnoredChanges(original);
|
|
|
|
const currentTimepicker: any = find((currentClean as any).nav, { type: 'timepicker' });
|
|
const originalTimepicker: any = find((originalClean as any).nav, { type: 'timepicker' });
|
|
|
|
if (currentTimepicker && originalTimepicker) {
|
|
currentTimepicker.now = originalTimepicker.now;
|
|
}
|
|
|
|
const currentJson = angular.toJson(currentClean);
|
|
const originalJson = angular.toJson(originalClean);
|
|
|
|
return currentJson !== originalJson;
|
|
}
|
|
}
|