mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Fixes save drawer always comparing changes against first loaded version (#76506)
* Dashboard: Fixes save changes diff after first save * Lots of type issues * better fix * Update some more places to use new function * Fix * Update * Update * remove console.log * Update
This commit is contained in:
parent
0d55dad075
commit
b01cbc7aef
@ -3112,14 +3112,13 @@ exports[`better eslint`] = {
|
||||
],
|
||||
"public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "6"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"]
|
||||
],
|
||||
"public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
@ -3550,13 +3549,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "23"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "24"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "25"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "26"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "27"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "28"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "29"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "30"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "31"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "32"]
|
||||
[0, 0, 0, "Do not use any type assertions.", "26"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "27"]
|
||||
],
|
||||
"public/app/features/dashboard/state/PanelModel.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
@ -43,7 +43,7 @@ class UnThemedTestRuleResult extends PureComponent<Props, State> {
|
||||
const { dashboard, panel } = this.props;
|
||||
|
||||
// dashboard save model
|
||||
const model = dashboard.getSaveModelClone();
|
||||
const model = dashboard.getSaveModelCloneOld();
|
||||
|
||||
// now replace panel to get current edits
|
||||
model.panels = model.panels.map((dashPanel) => {
|
||||
|
@ -83,7 +83,7 @@ export class DashboardExporter {
|
||||
// this is pretty hacky and needs to be changed
|
||||
dashboard.cleanUpRepeats();
|
||||
|
||||
const saveModel = dashboard.getSaveModelClone();
|
||||
const saveModel = dashboard.getSaveModelCloneOld();
|
||||
saveModel.id = null;
|
||||
|
||||
// undo repeat cleanup
|
||||
|
@ -35,7 +35,7 @@ function getTestContext() {
|
||||
const contextSrv = { isSignedIn: true, isEditor: true } as ContextSrv;
|
||||
setContextSrv(contextSrv);
|
||||
const dash = getDefaultDashboardModel();
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
|
||||
return { dash, original, contextSrv };
|
||||
}
|
||||
@ -98,7 +98,7 @@ describe('DashboardPrompt', () => {
|
||||
it('then it should return true', () => {
|
||||
const { contextSrv } = getTestContext();
|
||||
const dash = createDashboardModelFixture({}, { canSave: false });
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
contextSrv.isEditor = false;
|
||||
expect(ignoreChanges(dash, original)).toBe(true);
|
||||
});
|
||||
@ -108,7 +108,7 @@ describe('DashboardPrompt', () => {
|
||||
it('then it should return undefined', () => {
|
||||
const { contextSrv } = getTestContext();
|
||||
const dash = createDashboardModelFixture({}, { canSave: true });
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
contextSrv.isEditor = false;
|
||||
expect(ignoreChanges(dash, original)).toBe(undefined);
|
||||
});
|
||||
@ -118,7 +118,7 @@ describe('DashboardPrompt', () => {
|
||||
it('then it should return true', () => {
|
||||
const { contextSrv } = getTestContext();
|
||||
const dash = createDashboardModelFixture({}, { canSave: true });
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
contextSrv.isSignedIn = false;
|
||||
expect(ignoreChanges(dash, original)).toBe(true);
|
||||
});
|
||||
@ -127,7 +127,7 @@ describe('DashboardPrompt', () => {
|
||||
describe('when called with fromScript', () => {
|
||||
it('then it should return true', () => {
|
||||
const dash = createDashboardModelFixture({}, { canSave: true, fromScript: true, fromFile: undefined });
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
expect(ignoreChanges(dash, original)).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -146,7 +146,7 @@ describe('DashboardPrompt', () => {
|
||||
describe('when called with fromFile', () => {
|
||||
it('then it should return true', () => {
|
||||
const dash = createDashboardModelFixture({}, { canSave: true, fromScript: undefined, fromFile: true });
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
expect(ignoreChanges(dash, original)).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -154,7 +154,7 @@ describe('DashboardPrompt', () => {
|
||||
describe('when called with canSave but without fromScript and fromFile', () => {
|
||||
it('then it should return false', () => {
|
||||
const dash = createDashboardModelFixture({}, { canSave: true, fromScript: undefined, fromFile: undefined });
|
||||
const original = dash.getSaveModelClone();
|
||||
const original = dash.getSaveModelCloneOld();
|
||||
expect(ignoreChanges(dash, original)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as H from 'history';
|
||||
import { each, find } from 'lodash';
|
||||
import { find } from 'lodash';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Prompt } from 'react-router-dom';
|
||||
|
||||
@ -37,12 +37,12 @@ export const DashboardPrompt = React.memo(({ dashboard }: Props) => {
|
||||
// This is to minimize unsaved changes warnings due to automatic schema migrations
|
||||
const timeoutId = setTimeout(() => {
|
||||
const originalPath = locationService.getLocation().pathname;
|
||||
const original = dashboard.getSaveModelClone();
|
||||
const original = dashboard.getSaveModelCloneOld();
|
||||
setState({ originalPath, original });
|
||||
}, 1000);
|
||||
|
||||
const savedEventUnsub = appEvents.subscribe(DashboardSavedEvent, () => {
|
||||
const original = dashboard.getSaveModelClone();
|
||||
const original = dashboard.getSaveModelCloneOld();
|
||||
setState({ originalPath, original });
|
||||
});
|
||||
|
||||
@ -177,19 +177,22 @@ function cleanDashboardFromIgnoredChanges(dashData: Dashboard) {
|
||||
const dash = model.getSaveModelClone();
|
||||
|
||||
// ignore time and refresh
|
||||
dash.time = 0;
|
||||
delete dash.time;
|
||||
dash.refresh = '';
|
||||
dash.schemaVersion = 0;
|
||||
dash.timezone = 0;
|
||||
delete dash.timezone;
|
||||
|
||||
dash.panels = [];
|
||||
|
||||
// ignore template variable values
|
||||
each(dash.getVariables(), (variable: any) => {
|
||||
variable.current = null;
|
||||
variable.options = null;
|
||||
variable.filters = null;
|
||||
});
|
||||
if (dash.templating?.list) {
|
||||
for (const variable of dash.templating.list) {
|
||||
delete variable.current;
|
||||
delete variable.options;
|
||||
// @ts-expect-error
|
||||
delete variable.filters;
|
||||
}
|
||||
}
|
||||
|
||||
return dash;
|
||||
}
|
||||
@ -200,7 +203,7 @@ export function hasChanges(current: DashboardModel, original: unknown) {
|
||||
return true;
|
||||
}
|
||||
// TODO: Make getSaveModelClone return Dashboard type instead
|
||||
const currentClean = cleanDashboardFromIgnoredChanges(current.getSaveModelClone() as unknown as Dashboard);
|
||||
const currentClean = cleanDashboardFromIgnoredChanges(current.getSaveModelCloneOld() as unknown as Dashboard);
|
||||
const originalClean = cleanDashboardFromIgnoredChanges(original as Dashboard);
|
||||
|
||||
const currentTimepicker = find((currentClean as any).nav, { type: 'timepicker' });
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||
|
||||
import { DashboardModel } from '../../state';
|
||||
@ -15,16 +16,14 @@ type SaveDashboardDrawerProps = {
|
||||
dashboard: DashboardModel;
|
||||
onDismiss: () => void;
|
||||
dashboardJson: string;
|
||||
onSave: (clone: DashboardModel) => Promise<unknown>;
|
||||
onSave: (clone: Dashboard) => Promise<unknown>;
|
||||
};
|
||||
|
||||
export const SaveDashboardDrawer = ({ dashboard, onDismiss, dashboardJson, onSave }: SaveDashboardDrawerProps) => {
|
||||
const data = useMemo<SaveDashboardData>(() => {
|
||||
const clone = dashboard.getSaveModelClone();
|
||||
const cloneJSON = JSON.stringify(clone, null, 2);
|
||||
const cloneSafe = JSON.parse(cloneJSON); // avoids undefined issues
|
||||
|
||||
const diff = jsonDiff(JSON.parse(JSON.stringify(dashboardJson, null, 2)), cloneSafe);
|
||||
const diff = jsonDiff(JSON.parse(JSON.stringify(dashboardJson, null, 2)), clone);
|
||||
let diffCount = 0;
|
||||
for (const d of Object.values(diff)) {
|
||||
diffCount += d.length;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Button, Form } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
@ -10,7 +11,7 @@ import { SaveDashboardData } from '../SaveDashboard/types';
|
||||
interface SaveDashboardProps {
|
||||
dashboard: DashboardModel;
|
||||
onCancel: () => void;
|
||||
onSubmit?: (clone: DashboardModel) => Promise<unknown>;
|
||||
onSubmit?: (clone: Dashboard) => Promise<unknown>;
|
||||
onSuccess: () => void;
|
||||
saveModel: SaveDashboardData;
|
||||
}
|
||||
|
@ -45,9 +45,10 @@ export function getDashboardChanges(dashboard: DashboardModel): {
|
||||
migrationChanges: Diffs;
|
||||
} {
|
||||
// Re-parse the dashboard to remove functions and other non-serializable properties
|
||||
const currentDashboard = JSON.parse(JSON.stringify(dashboard.getSaveModelClone()));
|
||||
const currentDashboard = dashboard.getSaveModelClone();
|
||||
const originalDashboard = dashboard.getOriginalDashboard()!;
|
||||
const dashboardAfterMigration = JSON.parse(JSON.stringify(new DashboardModel(originalDashboard).getSaveModelClone()));
|
||||
|
||||
const dashboardAfterMigration = new DashboardModel(originalDashboard).getSaveModelClone();
|
||||
|
||||
return {
|
||||
userChanges: jsonDiff(dashboardAfterMigration, currentDashboard),
|
||||
|
@ -18,7 +18,7 @@ type ValidationResponse = Awaited<ReturnType<typeof backendSrv.validateDashboard
|
||||
function DashboardValidation({ dashboard }: DashboardValidationProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { loading, value, error } = useAsync(async () => {
|
||||
const saveModel = dashboard.getSaveModelClone();
|
||||
const saveModel = dashboard.getSaveModelCloneOld();
|
||||
const respPromise = backendSrv
|
||||
.validateDashboard(saveModel)
|
||||
// API returns schema validation errors in 4xx range, so resolve them rather than throwing
|
||||
|
@ -30,10 +30,7 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
||||
return { clone, diff: {}, diffCount: 0, hasChanges: false };
|
||||
}
|
||||
|
||||
const cloneJSON = JSON.stringify(clone, null, 2);
|
||||
const cloneSafe = JSON.parse(cloneJSON); // avoids undefined issues
|
||||
|
||||
const diff = jsonDiff(previous, cloneSafe);
|
||||
const diff = jsonDiff(previous, clone);
|
||||
let diffCount = 0;
|
||||
for (const d of Object.values(diff)) {
|
||||
diffCount += d.length;
|
||||
@ -48,7 +45,7 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
||||
}, [dashboard, previous, options, isNew]);
|
||||
|
||||
const [showDiff, setShowDiff] = useState(false);
|
||||
const { state, onDashboardSave } = useDashboardSave(dashboard, isCopy);
|
||||
const { state, onDashboardSave } = useDashboardSave(isCopy);
|
||||
const onSuccess = onSaveSuccess
|
||||
? () => {
|
||||
onDismiss();
|
||||
|
@ -3,8 +3,10 @@ import React, { useEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { FetchError } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Button, ConfirmModal, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
import { SaveDashboardAsButton } from './SaveDashboardButton';
|
||||
import { SaveDashboardModalProps } from './types';
|
||||
@ -14,7 +16,7 @@ interface SaveDashboardErrorProxyProps {
|
||||
/** original dashboard */
|
||||
dashboard: DashboardModel;
|
||||
/** dashboard save model with applied modifications, i.e. title */
|
||||
dashboardSaveModel: DashboardModel;
|
||||
dashboardSaveModel: Dashboard;
|
||||
error: FetchError;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
@ -25,7 +27,7 @@ export const SaveDashboardErrorProxy = ({
|
||||
error,
|
||||
onDismiss,
|
||||
}: SaveDashboardErrorProxyProps) => {
|
||||
const { onDashboardSave } = useDashboardSave(dashboard);
|
||||
const { onDashboardSave } = useDashboardSave();
|
||||
|
||||
useEffect(() => {
|
||||
if (error.data && proxyHandlesError(error.data.status)) {
|
||||
@ -78,7 +80,7 @@ export const SaveDashboardErrorProxy = ({
|
||||
};
|
||||
|
||||
const ConfirmPluginDashboardSaveModal = ({ onDismiss, dashboard }: SaveDashboardModalProps) => {
|
||||
const { onDashboardSave } = useDashboardSave(dashboard);
|
||||
const { onDashboardSave } = useDashboardSave();
|
||||
const styles = useStyles2(getConfirmPluginDashboardSaveModalStyles);
|
||||
|
||||
return (
|
||||
|
@ -3,7 +3,7 @@ import React, { ChangeEvent } from 'react';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, Input, Switch, Form, Field, InputControl, HorizontalGroup, Label, TextArea } from '@grafana/ui';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
|
||||
|
||||
import { GenAIDashDescriptionButton } from '../../GenAI/GenAIDashDescriptionButton';
|
||||
@ -26,11 +26,14 @@ const getSaveAsDashboardClone = (dashboard: DashboardModel) => {
|
||||
|
||||
// remove alerts if source dashboard is already persisted
|
||||
// do not want to create alert dupes
|
||||
if (dashboard.id > 0) {
|
||||
clone.panels.forEach((panel: PanelModel) => {
|
||||
if (dashboard.id > 0 && clone.panels) {
|
||||
clone.panels.forEach((panel) => {
|
||||
// @ts-expect-error
|
||||
if (panel.type === 'graph' && panel.alert) {
|
||||
// @ts-expect-error
|
||||
delete panel.thresholds;
|
||||
}
|
||||
// @ts-expect-error
|
||||
delete panel.alert;
|
||||
});
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { screen, render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
|
||||
|
||||
@ -15,22 +16,23 @@ const prepareDashboardMock = (
|
||||
resetTimeSpy: jest.Mock,
|
||||
resetVarsSpy: jest.Mock
|
||||
) => {
|
||||
const json = {
|
||||
const json: Dashboard = {
|
||||
title: 'name',
|
||||
hasTimeChanged: jest.fn().mockReturnValue(timeChanged),
|
||||
hasVariableValuesChanged: jest.fn().mockReturnValue(variableValuesChanged),
|
||||
resetOriginalTime: () => resetTimeSpy(),
|
||||
resetOriginalVariables: () => resetVarsSpy(),
|
||||
getSaveModelClone: jest.fn().mockReturnValue({}),
|
||||
id: 5,
|
||||
schemaVersion: 30,
|
||||
};
|
||||
|
||||
return {
|
||||
id: 5,
|
||||
meta: {},
|
||||
...json,
|
||||
meta: {},
|
||||
hasTimeChanged: jest.fn().mockReturnValue(timeChanged),
|
||||
hasVariablesChanged: jest.fn().mockReturnValue(variableValuesChanged),
|
||||
resetOriginalTime: () => resetTimeSpy(),
|
||||
resetOriginalVariables: () => resetVarsSpy(),
|
||||
getSaveModelClone: () => json,
|
||||
} as unknown as DashboardModel;
|
||||
};
|
||||
|
||||
const renderAndSubmitForm = async (dashboard: DashboardModel, submitSpy: jest.Mock) => {
|
||||
render(
|
||||
<SaveDashboardForm
|
||||
@ -43,7 +45,7 @@ const renderAndSubmitForm = async (dashboard: DashboardModel, submitSpy: jest.Mo
|
||||
return { status: 'success' };
|
||||
}}
|
||||
saveModel={{
|
||||
clone: dashboard,
|
||||
clone: dashboard.getSaveModelClone(),
|
||||
diff: {},
|
||||
diffCount: 0,
|
||||
hasChanges: true,
|
||||
@ -71,7 +73,7 @@ describe('SaveDashboardAsForm', () => {
|
||||
return {};
|
||||
}}
|
||||
saveModel={{
|
||||
clone: prepareDashboardMock(true, true, jest.fn(), jest.fn()),
|
||||
clone: { id: 1, schemaVersion: 3 },
|
||||
diff: {},
|
||||
diffCount: 0,
|
||||
hasChanges: true,
|
||||
@ -134,7 +136,7 @@ describe('SaveDashboardAsForm', () => {
|
||||
return {};
|
||||
}}
|
||||
saveModel={{
|
||||
clone: createDashboardModelFixture(),
|
||||
clone: createDashboardModelFixture().getSaveModelClone(),
|
||||
diff: {},
|
||||
diffCount: 0,
|
||||
hasChanges: true,
|
||||
|
@ -5,6 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { Button, Checkbox, Form, TextArea, useStyles2 } from '@grafana/ui';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
@ -21,7 +22,7 @@ export type SaveProps = {
|
||||
saveModel: SaveDashboardData; // already cloned
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
onSubmit?: (clone: DashboardModel, options: SaveDashboardOptions, dashboard: DashboardModel) => Promise<any>;
|
||||
onSubmit?: (saveModel: Dashboard, options: SaveDashboardOptions, dashboard: DashboardModel) => Promise<any>;
|
||||
options: SaveDashboardOptions;
|
||||
onOptionsChange: (opts: SaveDashboardOptions) => void;
|
||||
};
|
||||
@ -37,7 +38,7 @@ export const SaveDashboardForm = ({
|
||||
onOptionsChange,
|
||||
}: SaveProps) => {
|
||||
const hasTimeChanged = useMemo(() => dashboard.hasTimeChanged(), [dashboard]);
|
||||
const hasVariableChanged = useMemo(() => dashboard.hasVariableValuesChanged(), [dashboard]);
|
||||
const hasVariableChanged = useMemo(() => dashboard.hasVariablesChanged(), [dashboard]);
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState(options.message);
|
||||
@ -53,12 +54,6 @@ export const SaveDashboardForm = ({
|
||||
options = { ...options, message };
|
||||
const result = await onSubmit(saveModel.clone, options, dashboard);
|
||||
if (result.status === 'success') {
|
||||
if (options.saveVariables) {
|
||||
dashboard.resetOriginalVariables();
|
||||
}
|
||||
if (options.saveTimerange) {
|
||||
dashboard.resetOriginalTime();
|
||||
}
|
||||
onSuccess();
|
||||
} else {
|
||||
setSaving(false);
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { CloneOptions, DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { DashboardDataDTO } from 'app/types';
|
||||
|
||||
import { Diffs } from '../VersionHistory/utils';
|
||||
|
||||
export interface SaveDashboardData {
|
||||
clone: DashboardModel; // cloned copy
|
||||
clone: Dashboard; // cloned copy
|
||||
diff: Diffs;
|
||||
diffCount: number; // cumulative count
|
||||
hasChanges: boolean; // not new and has changes
|
||||
@ -29,7 +30,7 @@ export interface SaveDashboardFormProps {
|
||||
isLoading: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
onSubmit?: (clone: DashboardModel, options: SaveDashboardOptions, dashboard: DashboardModel) => Promise<any>;
|
||||
onSubmit?: (saveModel: Dashboard, options: SaveDashboardOptions, dashboard: DashboardModel) => Promise<any>;
|
||||
}
|
||||
|
||||
export interface SaveDashboardModalProps {
|
||||
|
@ -2,6 +2,7 @@ import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { locationUtil } from '@grafana/data';
|
||||
import { locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
@ -49,16 +50,18 @@ const saveDashboard = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const useDashboardSave = (dashboard: DashboardModel, isCopy = false) => {
|
||||
export const useDashboardSave = (isCopy = false) => {
|
||||
const dispatch = useDispatch();
|
||||
const notifyApp = useAppNotification();
|
||||
const [saveDashboardRtkQuery] = useSaveDashboardMutation();
|
||||
const [state, onDashboardSave] = useAsyncFn(
|
||||
async (clone: DashboardModel, options: SaveDashboardOptions, dashboard: DashboardModel) => {
|
||||
async (clone: Dashboard, options: SaveDashboardOptions, dashboard: DashboardModel) => {
|
||||
try {
|
||||
const result = await saveDashboard(clone, options, dashboard, saveDashboardRtkQuery);
|
||||
dashboard.version = result.version;
|
||||
dashboard.clearUnsavedChanges();
|
||||
|
||||
clone.version = result.version;
|
||||
dashboard.clearUnsavedChanges(clone, options);
|
||||
|
||||
// important that these happen before location redirect below
|
||||
appEvents.publish(new DashboardSavedEvent());
|
||||
|
@ -97,7 +97,7 @@ export class ShareSnapshot extends PureComponent<Props, State> {
|
||||
|
||||
saveSnapshot = async (dashboard: DashboardModel, external?: boolean) => {
|
||||
const { snapshotExpires } = this.state;
|
||||
const dash = this.dashboard.getSaveModelClone();
|
||||
const dash = this.dashboard.getSaveModelCloneOld();
|
||||
|
||||
this.scrubDashboard(dash);
|
||||
|
||||
|
@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { TimeZone } from '@grafana/schema';
|
||||
import { Dashboard, TimeZone } from '@grafana/schema';
|
||||
import { Button, ModalsController, PageToolbar, useStyles2 } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
@ -105,7 +105,7 @@ const Toolbar = ({ dashboard, dashboardJson }: ToolbarProps) => {
|
||||
dispatch(updateTimeZoneForSession(timeZone));
|
||||
};
|
||||
|
||||
const saveDashboard = async (clone: DashboardModel) => {
|
||||
const saveDashboard = async (clone: Dashboard) => {
|
||||
const params = locationService.getSearch();
|
||||
const serverPort = params.get('serverPort');
|
||||
if (!clone || !serverPort) {
|
||||
|
@ -130,7 +130,7 @@ describe('DashboardModel', () => {
|
||||
const saveModel = model.getSaveModelClone();
|
||||
const panels = saveModel.panels;
|
||||
|
||||
expect(panels.length).toBe(1);
|
||||
expect(panels!.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should save model in edit mode', () => {
|
||||
@ -140,7 +140,7 @@ describe('DashboardModel', () => {
|
||||
const panel = model.initEditPanel(model.panels[0]);
|
||||
panel.title = 'updated';
|
||||
|
||||
const saveModel = model.getSaveModelClone();
|
||||
const saveModel = model.getSaveModelCloneOld();
|
||||
const savedPanel = saveModel.panels[0];
|
||||
|
||||
expect(savedPanel.title).toBe('updated');
|
||||
@ -204,7 +204,7 @@ describe('DashboardModel', () => {
|
||||
});
|
||||
|
||||
it('getSaveModelClone should remove meta', () => {
|
||||
const clone = model.getSaveModelClone();
|
||||
const clone = model.getSaveModelCloneOld();
|
||||
expect(clone.meta).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@ -608,37 +608,32 @@ describe('DashboardModel', () => {
|
||||
const options = { saveTimerange: false };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
|
||||
expect(saveModel.time.from).toBe('now-6h');
|
||||
expect(saveModel.time.to).toBe('now');
|
||||
expect(saveModel.time!.from).toBe('now-6h');
|
||||
expect(saveModel.time!.to).toBe('now');
|
||||
});
|
||||
|
||||
it('getSaveModelClone should return updated time when saveTimerange=true', () => {
|
||||
const options = { saveTimerange: true };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
|
||||
expect(saveModel.time.from).toBe('now-3h');
|
||||
expect(saveModel.time.to).toBe('now-1h');
|
||||
});
|
||||
|
||||
it('hasTimeChanged should be false when reset original time', () => {
|
||||
model.resetOriginalTime();
|
||||
expect(model.hasTimeChanged()).toBeFalsy();
|
||||
expect(saveModel.time!.from).toBe('now-3h');
|
||||
expect(saveModel.time!.to).toBe('now-1h');
|
||||
});
|
||||
|
||||
it('getSaveModelClone should return original time when saveTimerange=false', () => {
|
||||
const options = { saveTimerange: false };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
|
||||
expect(saveModel.time.from).toBe('now-6h');
|
||||
expect(saveModel.time.to).toBe('now');
|
||||
expect(saveModel.time!.from).toBe('now-6h');
|
||||
expect(saveModel.time!.to).toBe('now');
|
||||
});
|
||||
|
||||
it('getSaveModelClone should return updated time when saveTimerange=true', () => {
|
||||
const options = { saveTimerange: true };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
|
||||
expect(saveModel.time.from).toBe('now-3h');
|
||||
expect(saveModel.time.to).toBe('now-1h');
|
||||
expect(saveModel.time!.from).toBe('now-3h');
|
||||
expect(saveModel.time!.to).toBe('now-1h');
|
||||
});
|
||||
|
||||
it('getSaveModelClone should remove repeated panels and scopedVars', () => {
|
||||
@ -684,13 +679,13 @@ describe('DashboardModel', () => {
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.dc?.value).toBe('dc1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app?.value).toBe('se1');
|
||||
|
||||
const saveModel = model.getSaveModelClone();
|
||||
const saveModel = model.getSaveModelCloneOld();
|
||||
expect(saveModel.panels.length).toBe(2);
|
||||
expect(saveModel.panels[0].scopedVars).toBe(undefined);
|
||||
expect(saveModel.panels[1].scopedVars).toBe(undefined);
|
||||
|
||||
model.collapseRows();
|
||||
const savedModelWithCollapsedRows = model.getSaveModelClone();
|
||||
const savedModelWithCollapsedRows = model.getSaveModelCloneOld();
|
||||
expect(savedModelWithCollapsedRows.panels[0].panels!.length).toBe(1);
|
||||
});
|
||||
|
||||
@ -738,14 +733,14 @@ describe('DashboardModel', () => {
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app?.value).toBe('se1');
|
||||
|
||||
model.snapshot = { timestamp: new Date() };
|
||||
const saveModel = model.getSaveModelClone();
|
||||
const saveModel = model.getSaveModelCloneOld();
|
||||
expect(saveModel.panels.filter((x) => x.type === 'row')).toHaveLength(2);
|
||||
expect(saveModel.panels.filter((x) => x.type !== 'row')).toHaveLength(4);
|
||||
expect(saveModel.panels.find((x) => x.type !== 'row')?.scopedVars?.dc?.value).toBe('dc1');
|
||||
expect(saveModel.panels.find((x) => x.type !== 'row')?.scopedVars?.app?.value).toBe('se1');
|
||||
|
||||
model.collapseRows();
|
||||
const savedModelWithCollapsedRows = model.getSaveModelClone();
|
||||
const savedModelWithCollapsedRows = model.getSaveModelCloneOld();
|
||||
expect(savedModelWithCollapsedRows.panels[0].panels!.length).toBe(2);
|
||||
});
|
||||
});
|
||||
@ -760,6 +755,8 @@ describe('DashboardModel', () => {
|
||||
{
|
||||
name: 'Server',
|
||||
type: 'query',
|
||||
refresh: 1,
|
||||
options: [],
|
||||
current: {
|
||||
selected: true,
|
||||
text: 'server_001',
|
||||
@ -770,10 +767,10 @@ describe('DashboardModel', () => {
|
||||
},
|
||||
};
|
||||
model = getDashboardModel(json);
|
||||
expect(model.hasVariableValuesChanged()).toBeFalsy();
|
||||
expect(model.hasVariablesChanged()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be false when adding a template variable', () => {
|
||||
it('hasVariablesChanged should be false when adding a template variable', () => {
|
||||
model.templating.list.push({
|
||||
name: 'Server2',
|
||||
type: 'query',
|
||||
@ -783,24 +780,24 @@ describe('DashboardModel', () => {
|
||||
value: 'server_002',
|
||||
},
|
||||
});
|
||||
expect(model.hasVariableValuesChanged()).toBeFalsy();
|
||||
expect(model.hasVariablesChanged()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be false when removing existing template variable', () => {
|
||||
it('hasVariablesChanged should be false when removing existing template variable', () => {
|
||||
model.templating.list = [];
|
||||
expect(model.hasVariableValuesChanged()).toBeFalsy();
|
||||
expect(model.hasVariablesChanged()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be true when changing value of template variable', () => {
|
||||
it('hasVariablesChanged should be true when changing value of template variable', () => {
|
||||
model.templating.list[0].current.text = 'server_002';
|
||||
expect(model.hasVariableValuesChanged()).toBeTruthy();
|
||||
expect(model.hasVariablesChanged()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('getSaveModelClone should return original variable when saveVariables=false', () => {
|
||||
model.templating.list[0].current.text = 'server_002';
|
||||
|
||||
const options = { saveVariables: false };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
const saveModel = model.getSaveModelCloneOld(options);
|
||||
|
||||
expect(saveModel.templating.list[0].current.text).toBe('server_001');
|
||||
});
|
||||
@ -809,7 +806,7 @@ describe('DashboardModel', () => {
|
||||
model.templating.list[0].current.text = 'server_002';
|
||||
|
||||
const options = { saveVariables: true };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
const saveModel = model.getSaveModelCloneOld(options);
|
||||
|
||||
expect(saveModel.templating.list[0].current.text).toBe('server_002');
|
||||
});
|
||||
@ -825,6 +822,7 @@ describe('DashboardModel', () => {
|
||||
{
|
||||
name: 'Filter',
|
||||
type: 'adhoc',
|
||||
refresh: 0,
|
||||
filters: [
|
||||
{
|
||||
key: '@hostname',
|
||||
@ -837,10 +835,10 @@ describe('DashboardModel', () => {
|
||||
},
|
||||
};
|
||||
model = getDashboardModel(json);
|
||||
expect(model.hasVariableValuesChanged()).toBeFalsy();
|
||||
expect(model.hasVariablesChanged()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be false when adding a template variable', () => {
|
||||
it('hasVariablesChanged should be false when adding a template variable', () => {
|
||||
model.templating.list.push({
|
||||
name: 'Filter',
|
||||
type: 'adhoc',
|
||||
@ -852,34 +850,34 @@ describe('DashboardModel', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(model.hasVariableValuesChanged()).toBeFalsy();
|
||||
expect(model.hasVariablesChanged()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be false when removing existing template variable', () => {
|
||||
it('hasVariablesChanged should be false when removing existing template variable', () => {
|
||||
model.templating.list = [];
|
||||
expect(model.hasVariableValuesChanged()).toBeFalsy();
|
||||
expect(model.hasVariablesChanged()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be true when changing value of filter', () => {
|
||||
it('hasVariablesChanged should be true when changing value of filter', () => {
|
||||
model.templating.list[0].filters[0].value = 'server 1';
|
||||
expect(model.hasVariableValuesChanged()).toBeTruthy();
|
||||
expect(model.hasVariablesChanged()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hasVariableValuesChanged should be true when adding an additional condition', () => {
|
||||
it('hasVariablesChanged should be true when adding an additional condition', () => {
|
||||
model.templating.list[0].filters[0].condition = 'AND';
|
||||
model.templating.list[0].filters[1] = {
|
||||
key: '@metric',
|
||||
operator: '=',
|
||||
value: 'logins.count',
|
||||
};
|
||||
expect(model.hasVariableValuesChanged()).toBeTruthy();
|
||||
expect(model.hasVariablesChanged()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('getSaveModelClone should return original variable when saveVariables=false', () => {
|
||||
model.templating.list[0].filters[0].value = 'server 1';
|
||||
|
||||
const options = { saveVariables: false };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
const saveModel = model.getSaveModelCloneOld(options);
|
||||
|
||||
expect(saveModel.templating.list[0].filters[0].value).toBe('server 20');
|
||||
});
|
||||
@ -888,7 +886,7 @@ describe('DashboardModel', () => {
|
||||
model.templating.list[0].filters[0].value = 'server 1';
|
||||
|
||||
const options = { saveVariables: true };
|
||||
const saveModel = model.getSaveModelClone(options);
|
||||
const saveModel = model.getSaveModelCloneOld(options);
|
||||
|
||||
expect(saveModel.templating.list[0].filters[0].value).toBe('server 1');
|
||||
});
|
||||
|
@ -24,7 +24,6 @@ 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';
|
||||
@ -175,12 +174,12 @@ export class DashboardModel implements TimeModel {
|
||||
this.panels = map(data.panels ?? [], (panelData: any) => new PanelModel(panelData));
|
||||
// Deep clone original dashboard to avoid mutations by object reference
|
||||
this.originalDashboard = cloneDeep(data);
|
||||
this.originalTemplating = cloneDeep(this.templating);
|
||||
this.originalTime = cloneDeep(this.time);
|
||||
|
||||
this.ensurePanelsHaveUniqueIds();
|
||||
this.formatDate = this.formatDate.bind(this);
|
||||
|
||||
this.resetOriginalVariables(true);
|
||||
this.resetOriginalTime();
|
||||
|
||||
this.initMeta(meta);
|
||||
this.updateSchema(data);
|
||||
|
||||
@ -248,9 +247,11 @@ export class DashboardModel implements TimeModel {
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
// cleans meta data and other non persistent state
|
||||
getSaveModelClone(options?: CloneOptions): DashboardModel {
|
||||
const defaults = _defaults(options || {}, {
|
||||
/**
|
||||
* @deprecated Returns the wrong type please do not use
|
||||
*/
|
||||
getSaveModelCloneOld(options?: CloneOptions): DashboardModel {
|
||||
const optionsWithDefaults = _defaults(options || {}, {
|
||||
saveVariables: true,
|
||||
saveTimerange: true,
|
||||
});
|
||||
@ -265,9 +266,9 @@ export class DashboardModel implements TimeModel {
|
||||
copy[property] = cloneDeep(this[property]);
|
||||
}
|
||||
|
||||
this.updateTemplatingSaveModelClone(copy, defaults);
|
||||
copy.templating = this.getTemplatingSaveModel(optionsWithDefaults);
|
||||
|
||||
if (!defaults.saveTimerange) {
|
||||
if (!optionsWithDefaults.saveTimerange) {
|
||||
copy.time = this.originalTime;
|
||||
}
|
||||
|
||||
@ -281,6 +282,19 @@ export class DashboardModel implements TimeModel {
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the persisted save model (schema) of the dashboard
|
||||
*/
|
||||
getSaveModelClone(options?: CloneOptions): Dashboard {
|
||||
const clone = this.getSaveModelCloneOld(options);
|
||||
|
||||
// This is a bit messy / hacky but it's how we clean the model of any nulls / undefined / infinity
|
||||
const cloneJSON = JSON.stringify(clone);
|
||||
const cloneSafe = JSON.parse(cloneJSON);
|
||||
|
||||
return cloneSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will load a new dashboard, but keep existing panels unchanged
|
||||
*
|
||||
@ -353,36 +367,35 @@ export class DashboardModel implements TimeModel {
|
||||
});
|
||||
}
|
||||
|
||||
private updateTemplatingSaveModelClone(
|
||||
copy: any,
|
||||
defaults: { saveTimerange: boolean; saveVariables: boolean } & CloneOptions
|
||||
) {
|
||||
const originalVariables = this.originalTemplating;
|
||||
private getTemplatingSaveModel(options: CloneOptions) {
|
||||
const originalVariables = this.originalTemplating?.list ?? [];
|
||||
const currentVariables = this.getVariablesFromState(this.uid);
|
||||
|
||||
copy.templating = {
|
||||
list: currentVariables.map((variable) =>
|
||||
variableAdapters.get(variable.type).getSaveModel(variable, defaults.saveVariables)
|
||||
),
|
||||
};
|
||||
const saveModels = currentVariables.map((variable) => {
|
||||
const variableSaveModel = variableAdapters.get(variable.type).getSaveModel(variable, options.saveVariables);
|
||||
|
||||
if (!defaults.saveVariables) {
|
||||
for (const current of copy.templating.list) {
|
||||
if (!options.saveVariables) {
|
||||
const original = originalVariables.find(
|
||||
({ name, type }: any) => name === current.name && type === current.type
|
||||
({ name, type }: any) => name === variable.name && type === variable.type
|
||||
);
|
||||
|
||||
if (!original) {
|
||||
continue;
|
||||
return variableSaveModel;
|
||||
}
|
||||
|
||||
if (current.type === 'adhoc') {
|
||||
current.filters = original.filters;
|
||||
if (variable.type === 'adhoc') {
|
||||
variableSaveModel.filters = original.filters;
|
||||
} else {
|
||||
current.current = original.current;
|
||||
variableSaveModel.current = original.current;
|
||||
variableSaveModel.options = original.options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return variableSaveModel;
|
||||
});
|
||||
|
||||
const saveModelsWithoutNull = sortedDeepCloneWithoutNulls(saveModels);
|
||||
return { list: saveModelsWithoutNull };
|
||||
}
|
||||
|
||||
timeRangeUpdated(timeRange: TimeRange) {
|
||||
@ -567,7 +580,7 @@ export class DashboardModel implements TimeModel {
|
||||
});
|
||||
}
|
||||
|
||||
clearUnsavedChanges() {
|
||||
clearUnsavedChanges(savedModel: Dashboard, options: CloneOptions) {
|
||||
for (const panel of this.panels) {
|
||||
panel.configRev = 0;
|
||||
}
|
||||
@ -577,6 +590,13 @@ export class DashboardModel implements TimeModel {
|
||||
this.panelInEdit.hasSavedPanelEditChange = this.panelInEdit.configRev > 0;
|
||||
this.panelInEdit.configRev = 0;
|
||||
}
|
||||
|
||||
this.originalDashboard = savedModel;
|
||||
this.originalTemplating = savedModel.templating;
|
||||
|
||||
if (options.saveTimerange) {
|
||||
this.originalTime = savedModel.time;
|
||||
}
|
||||
}
|
||||
|
||||
hasUnsavedChanges() {
|
||||
@ -1082,10 +1102,6 @@ export class DashboardModel implements TimeModel {
|
||||
migrator.updateSchema(old);
|
||||
}
|
||||
|
||||
resetOriginalTime() {
|
||||
this.originalTime = deepFreeze(this.time);
|
||||
}
|
||||
|
||||
hasTimeChanged() {
|
||||
const { time, originalTime } = this;
|
||||
|
||||
@ -1097,19 +1113,6 @@ export class DashboardModel implements TimeModel {
|
||||
);
|
||||
}
|
||||
|
||||
resetOriginalVariables(initial = false) {
|
||||
if (initial) {
|
||||
this.originalTemplating = this.cloneVariablesFrom(this.templating.list);
|
||||
return;
|
||||
}
|
||||
|
||||
this.originalTemplating = this.cloneVariablesFrom(this.getVariablesFromState(this.uid));
|
||||
}
|
||||
|
||||
hasVariableValuesChanged() {
|
||||
return this.hasVariablesChanged(this.originalTemplating, this.getVariablesFromState(this.uid));
|
||||
}
|
||||
|
||||
autoFitPanels(viewHeight: number, kioskMode?: UrlQueryValue) {
|
||||
const currentGridHeight = Math.max(...this.panels.map((panel) => panel.gridPos.h + panel.gridPos.y));
|
||||
|
||||
@ -1245,28 +1248,15 @@ export class DashboardModel implements TimeModel {
|
||||
return this.getVariablesFromState(this.uid).length > 0;
|
||||
}
|
||||
|
||||
private hasVariablesChanged(originalVariables: any[], currentVariables: any[]): boolean {
|
||||
public hasVariablesChanged(): boolean {
|
||||
const originalVariables = this.originalTemplating?.list ?? [];
|
||||
const currentVariables = this.getTemplatingSaveModel({ saveVariables: true }).list;
|
||||
|
||||
if (originalVariables.length !== currentVariables.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const updated = currentVariables.map((variable: any) => ({
|
||||
name: variable.name,
|
||||
type: variable.type,
|
||||
current: cloneDeep(variable.current),
|
||||
filters: cloneDeep(variable.filters),
|
||||
}));
|
||||
|
||||
return !isEqual(updated, originalVariables);
|
||||
}
|
||||
|
||||
private cloneVariablesFrom(variables: any[]) {
|
||||
return variables.map((variable) => ({
|
||||
name: variable.name,
|
||||
type: variable.type,
|
||||
current: deepFreeze(variable.current),
|
||||
filters: deepFreeze(variable.filters),
|
||||
}));
|
||||
return !isEqual(currentVariables, originalVariables);
|
||||
}
|
||||
|
||||
private variablesTimeRangeProcessDoneHandler(event: VariablesTimeRangeProcessDone) {
|
||||
|
@ -41,7 +41,7 @@ describe('Merge dashboard panels', () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
rawPanels = dashboard.getSaveModelClone().panels;
|
||||
rawPanels = dashboard.getSaveModelCloneOld().panels;
|
||||
});
|
||||
|
||||
it('should load and support noop', () => {
|
||||
|
@ -29,7 +29,7 @@ export function initWindowRuntime() {
|
||||
if (!d) {
|
||||
return undefined;
|
||||
}
|
||||
return d.getSaveModelClone();
|
||||
return d.getSaveModelCloneOld();
|
||||
},
|
||||
|
||||
/** The selected time range */
|
||||
|
@ -193,7 +193,7 @@ export const createUsagesNetwork = (variables: VariableModel[], dashboard: Dashb
|
||||
|
||||
const unUsed: VariableModel[] = [];
|
||||
let usages: VariableUsageTree[] = [];
|
||||
const model = dashboard.getSaveModelClone();
|
||||
const model = dashboard.getSaveModelCloneOld();
|
||||
|
||||
for (const variable of variables) {
|
||||
const variableId = variable.id;
|
||||
@ -233,7 +233,7 @@ function createUnknownsNetwork(variables: VariableModel[], dashboard: DashboardM
|
||||
}
|
||||
|
||||
let unknown: VariableUsageTree[] = [];
|
||||
const model = dashboard.getSaveModelClone();
|
||||
const model = dashboard.getSaveModelCloneOld();
|
||||
|
||||
const unknownVariables = getUnknownVariableStrings(variables, model);
|
||||
for (const unknownVariable of unknownVariables) {
|
||||
|
Loading…
Reference in New Issue
Block a user