mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Store original JSON in DashboardModel (#73881)
* Dashboard: Save original JSON to state * Dashboards: Update tests * Dashboards: Fix original json access * Dashboard: Save original to the DashboardModel * Dashboard: Cleanup tests * Remove original db setter
This commit is contained in:
parent
c761f2e4d9
commit
c9323e303d
@ -22,12 +22,6 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv', () => ({
|
|
||||||
backendSrv: {
|
|
||||||
getDashboardByUid: jest.fn().mockResolvedValue({ dashboard: {} }),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const store = configureStore();
|
const store = configureStore();
|
||||||
const mockPost = jest.fn();
|
const mockPost = jest.fn();
|
||||||
const buildMocks = () => ({
|
const buildMocks = () => ({
|
||||||
@ -76,7 +70,7 @@ describe('SaveDashboardDrawer', () => {
|
|||||||
expect(screen.getByRole('button', { name: /overwrite/i })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: /overwrite/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render corresponding save modal once the errror is handled', async () => {
|
it('should render corresponding save modal once the error is handled', async () => {
|
||||||
const { onDismiss, dashboard, error } = buildMocks();
|
const { onDismiss, dashboard, error } = buildMocks();
|
||||||
mockPost.mockRejectedValueOnce(error);
|
mockPost.mockRejectedValueOnce(error);
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useAsync } from 'react-use';
|
|
||||||
|
|
||||||
import { config, isFetchError } from '@grafana/runtime';
|
import { config, isFetchError } from '@grafana/runtime';
|
||||||
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
|
||||||
|
|
||||||
import { jsonDiff } from '../VersionHistory/utils';
|
import { jsonDiff } from '../VersionHistory/utils';
|
||||||
|
|
||||||
@ -18,33 +16,24 @@ import { useDashboardSave } from './useDashboardSave';
|
|||||||
|
|
||||||
export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCopy }: SaveDashboardModalProps) => {
|
export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCopy }: SaveDashboardModalProps) => {
|
||||||
const [options, setOptions] = useState<SaveDashboardOptions>({});
|
const [options, setOptions] = useState<SaveDashboardOptions>({});
|
||||||
|
const previous = dashboard.getOriginalDashboard();
|
||||||
const isProvisioned = dashboard.meta.provisioned;
|
const isProvisioned = dashboard.meta.provisioned;
|
||||||
const isNew = dashboard.version === 0;
|
const isNew = dashboard.version === 0;
|
||||||
|
|
||||||
const previous = useAsync(async () => {
|
|
||||||
if (isNew) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await backendSrv.getDashboardByUid(dashboard.uid);
|
|
||||||
return result.dashboard;
|
|
||||||
}, [dashboard, isNew]);
|
|
||||||
|
|
||||||
const data = useMemo<SaveDashboardData>(() => {
|
const data = useMemo<SaveDashboardData>(() => {
|
||||||
const clone = dashboard.getSaveModelClone({
|
const clone = dashboard.getSaveModelClone({
|
||||||
saveTimerange: Boolean(options.saveTimerange),
|
saveTimerange: Boolean(options.saveTimerange),
|
||||||
saveVariables: Boolean(options.saveVariables),
|
saveVariables: Boolean(options.saveVariables),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!previous.value) {
|
if (!previous) {
|
||||||
return { clone, diff: {}, diffCount: 0, hasChanges: false };
|
return { clone, diff: {}, diffCount: 0, hasChanges: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloneJSON = JSON.stringify(clone, null, 2);
|
const cloneJSON = JSON.stringify(clone, null, 2);
|
||||||
const cloneSafe = JSON.parse(cloneJSON); // avoids undefined issues
|
const cloneSafe = JSON.parse(cloneJSON); // avoids undefined issues
|
||||||
|
|
||||||
const diff = jsonDiff(previous.value, cloneSafe);
|
const diff = jsonDiff(previous, cloneSafe);
|
||||||
let diffCount = 0;
|
let diffCount = 0;
|
||||||
for (const d of Object.values(diff)) {
|
for (const d of Object.values(diff)) {
|
||||||
diffCount += d.length;
|
diffCount += d.length;
|
||||||
@ -56,7 +45,7 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
|||||||
diffCount,
|
diffCount,
|
||||||
hasChanges: diffCount > 0 && !isNew,
|
hasChanges: diffCount > 0 && !isNew,
|
||||||
};
|
};
|
||||||
}, [dashboard, previous.value, options, isNew]);
|
}, [dashboard, previous, options, isNew]);
|
||||||
|
|
||||||
const [showDiff, setShowDiff] = useState(false);
|
const [showDiff, setShowDiff] = useState(false);
|
||||||
const { state, onDashboardSave } = useDashboardSave(dashboard, isCopy);
|
const { state, onDashboardSave } = useDashboardSave(dashboard, isCopy);
|
||||||
@ -69,7 +58,7 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
|||||||
|
|
||||||
const renderSaveBody = () => {
|
const renderSaveBody = () => {
|
||||||
if (showDiff) {
|
if (showDiff) {
|
||||||
return <SaveDashboardDiff diff={data.diff} oldValue={previous.value} newValue={data.clone} />;
|
return <SaveDashboardDiff diff={data.diff} oldValue={previous} newValue={data.clone} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew || isCopy) {
|
if (isNew || isCopy) {
|
||||||
|
@ -124,6 +124,13 @@ const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partia
|
|||||||
return new DashboardModel(data, metaOverrides);
|
return new DashboardModel(data, metaOverrides);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dashboardBase = {
|
||||||
|
getModel: getTestDashboard,
|
||||||
|
initError: null,
|
||||||
|
initPhase: DashboardInitPhase.Completed,
|
||||||
|
permissions: [],
|
||||||
|
};
|
||||||
|
|
||||||
describe('PublicDashboardPage', () => {
|
describe('PublicDashboardPage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config.featureToggles.publicDashboards = true;
|
config.featureToggles.publicDashboards = true;
|
||||||
@ -144,12 +151,7 @@ describe('PublicDashboardPage', () => {
|
|||||||
|
|
||||||
describe('Given a simple public dashboard', () => {
|
describe('Given a simple public dashboard', () => {
|
||||||
const newState = {
|
const newState = {
|
||||||
dashboard: {
|
dashboard: dashboardBase,
|
||||||
getModel: getTestDashboard,
|
|
||||||
initError: null,
|
|
||||||
initPhase: DashboardInitPhase.Completed,
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it('Should render panels', async () => {
|
it('Should render panels', async () => {
|
||||||
@ -220,10 +222,8 @@ describe('PublicDashboardPage', () => {
|
|||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
...dashboardBase,
|
||||||
getModel: () => getTestDashboard({ panels }),
|
getModel: () => getTestDashboard({ panels }),
|
||||||
initError: null,
|
|
||||||
initPhase: DashboardInitPhase.Completed,
|
|
||||||
permissions: [],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setup(undefined, newState);
|
setup(undefined, newState);
|
||||||
@ -247,13 +247,11 @@ describe('PublicDashboardPage', () => {
|
|||||||
it('Should render time range and refresh picker buttons', async () => {
|
it('Should render time range and refresh picker buttons', async () => {
|
||||||
setup(undefined, {
|
setup(undefined, {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
...dashboardBase,
|
||||||
getModel: () =>
|
getModel: () =>
|
||||||
getTestDashboard({
|
getTestDashboard({
|
||||||
timepicker: { hidden: false, collapse: false, refresh_intervals: [], time_options: [] },
|
timepicker: { hidden: false, collapse: false, refresh_intervals: [], time_options: [] },
|
||||||
}),
|
}),
|
||||||
initError: null,
|
|
||||||
initPhase: DashboardInitPhase.Completed,
|
|
||||||
permissions: [],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(await screen.findByTestId(selectors.TimePicker.openButton)).toBeInTheDocument();
|
expect(await screen.findByTestId(selectors.TimePicker.openButton)).toBeInTheDocument();
|
||||||
@ -266,10 +264,8 @@ describe('PublicDashboardPage', () => {
|
|||||||
it('Should render public dashboard paused screen', async () => {
|
it('Should render public dashboard paused screen', async () => {
|
||||||
setup(undefined, {
|
setup(undefined, {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
...dashboardBase,
|
||||||
getModel: () => getTestDashboard(undefined, { publicDashboardEnabled: false, dashboardNotFound: false }),
|
getModel: () => getTestDashboard(undefined, { publicDashboardEnabled: false, dashboardNotFound: false }),
|
||||||
initError: null,
|
|
||||||
initPhase: DashboardInitPhase.Completed,
|
|
||||||
permissions: [],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -285,10 +281,8 @@ describe('PublicDashboardPage', () => {
|
|||||||
it('Should render public dashboard deleted screen', async () => {
|
it('Should render public dashboard deleted screen', async () => {
|
||||||
setup(undefined, {
|
setup(undefined, {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
...dashboardBase,
|
||||||
getModel: () => getTestDashboard(undefined, { dashboardNotFound: true }),
|
getModel: () => getTestDashboard(undefined, { dashboardNotFound: true }),
|
||||||
initError: null,
|
|
||||||
initPhase: DashboardInitPhase.Completed,
|
|
||||||
permissions: [],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ export class DashboardModel implements TimeModel {
|
|||||||
private appEventsSubscription: Subscription;
|
private appEventsSubscription: Subscription;
|
||||||
private lastRefresh: number;
|
private lastRefresh: number;
|
||||||
private timeRangeUpdatedDuringEdit = false;
|
private timeRangeUpdatedDuringEdit = false;
|
||||||
|
private originalDashboard: Dashboard | null = null;
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
// not persisted
|
// not persisted
|
||||||
@ -130,6 +131,7 @@ export class DashboardModel implements TimeModel {
|
|||||||
panelsAffectedByVariableChange: true,
|
panelsAffectedByVariableChange: true,
|
||||||
lastRefresh: true,
|
lastRefresh: true,
|
||||||
timeRangeUpdatedDuringEdit: true,
|
timeRangeUpdatedDuringEdit: true,
|
||||||
|
originalDashboard: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -169,6 +171,7 @@ export class DashboardModel implements TimeModel {
|
|||||||
this.links = data.links ?? [];
|
this.links = data.links ?? [];
|
||||||
this.gnetId = data.gnetId || null;
|
this.gnetId = data.gnetId || null;
|
||||||
this.panels = map(data.panels ?? [], (panelData: any) => new PanelModel(panelData));
|
this.panels = map(data.panels ?? [], (panelData: any) => new PanelModel(panelData));
|
||||||
|
this.originalDashboard = data;
|
||||||
this.ensurePanelsHaveUniqueIds();
|
this.ensurePanelsHaveUniqueIds();
|
||||||
this.formatDate = this.formatDate.bind(this);
|
this.formatDate = this.formatDate.bind(this);
|
||||||
|
|
||||||
@ -1296,6 +1299,10 @@ export class DashboardModel implements TimeModel {
|
|||||||
this.templateVariableValueUpdated();
|
this.templateVariableValueUpdated();
|
||||||
this.startRefresh(event.payload);
|
this.startRefresh(event.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOriginalDashboard() {
|
||||||
|
return this.originalDashboard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPanelWithLegend(panel: PanelModel): panel is PanelModel & Pick<Required<PanelModel>, 'legend'> {
|
function isPanelWithLegend(panel: PanelModel): panel is PanelModel & Pick<Required<PanelModel>, 'legend'> {
|
||||||
|
Loading…
Reference in New Issue
Block a user