DashboardScene: Fixes deleting dirty dashboard (#86479)

* DashboardScene: Fixes deleting dirty dashboard

* refactor + unit test
This commit is contained in:
Torkel Ödegaard 2024-04-23 12:35:16 +02:00 committed by GitHub
parent e6799be13c
commit 835b968b08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 125 additions and 5 deletions

View File

@ -1,4 +1,5 @@
import { CoreApp, LoadingState, getDefaultTimeRange } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
sceneGraph,
SceneGridLayout,
@ -71,6 +72,12 @@ jest.mock('app/features/playlist/PlaylistSrv', () => ({
stop: jest.fn(),
},
}));
jest.mock('app/features/manage-dashboards/state/actions', () => ({
...jest.requireActual('app/features/manage-dashboards/state/actions'),
deleteDashboard: jest.fn().mockResolvedValue({}),
}));
const worker = createWorker();
mockResultsOfDetectChangesWorker({ hasChanges: true, hasTimeChanges: false, hasVariableValueChanges: false });
@ -884,6 +891,18 @@ describe('DashboardScene', () => {
});
});
describe('Deleting dashboard', () => {
it('Should mark it non dirty before navigating to root', async () => {
const scene = buildTestScene();
scene.setState({ isDirty: true });
locationService.push('/d/adsdas');
await scene.deleteDashboard();
expect(scene.state.isDirty).toBe(false);
});
});
describe('Enriching data requests', () => {
let scene: DashboardScene;

View File

@ -24,6 +24,7 @@ import store from 'app/core/store';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
import { deleteDashboard } from 'app/features/manage-dashboards/state/actions';
import { VariablesChanged } from 'app/features/variables/types';
import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
import { ShowConfirmModalEvent } from 'app/types/events';
@ -832,6 +833,13 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
public setInitialSaveModel(saveModel: Dashboard) {
this._initialSaveModel = saveModel;
}
public async deleteDashboard() {
await deleteDashboard(this.state.uid!, true);
// Need to mark it non dirty to navigate away without unsaved changes warning
this.setState({ isDirty: false });
locationService.replace('/');
}
}
export class DashboardVariableDependency implements SceneVariableDependencyConfigLike {

View File

@ -0,0 +1,90 @@
import React from 'react';
import { useAsyncFn, useToggle } from 'react-use';
import { Button, ConfirmModal, Modal } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { DashboardScene } from '../scene/DashboardScene';
interface ButtonProps {
dashboard: DashboardScene;
}
export function DeleteDashboardButton({ dashboard }: ButtonProps) {
const [showModal, toggleModal] = useToggle(false);
return (
<>
<Button variant="destructive" onClick={toggleModal} aria-label="Dashboard settings page delete dashboard button">
<Trans i18nKey="dashboard-settings.dashboard-delete-button">Delete dashboard</Trans>
</Button>
{showModal && <DeleteDashboardModal dashboard={dashboard} onClose={toggleModal} />}
</>
);
}
interface ModalProps {
dashboard: DashboardScene;
onClose: () => void;
}
function DeleteDashboardModal({ dashboard, onClose }: ModalProps) {
const [, onConfirm] = useAsyncFn(async () => {
onClose();
await dashboard.deleteDashboard();
}, [dashboard, onClose]);
if (dashboard.state.meta.provisioned) {
return <ProvisionedDeleteModal dashboard={dashboard} onClose={onClose} />;
}
return (
<ConfirmModal
isOpen={true}
body={
<>
<p>Do you want to delete this dashboard?</p>
<p>{dashboard.state.title}</p>
</>
}
onConfirm={onConfirm}
onDismiss={onClose}
title="Delete"
icon="trash-alt"
confirmText="Delete"
/>
);
}
function ProvisionedDeleteModal({ dashboard, onClose }: ModalProps) {
return (
<Modal isOpen={true} title="Cannot delete provisioned dashboard" icon="trash-alt" onDismiss={onClose}>
<p>
This dashboard is managed by Grafana provisioning and cannot be deleted. Remove the dashboard from the config
file to delete it.
</p>
<p>
<i>
See{' '}
<a
className="external-link"
href="https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards"
target="_blank"
rel="noreferrer"
>
documentation
</a>{' '}
for more information about provisioning.
</i>
<br />
File path: {dashboard.state.meta.provisionedExternalId}
</p>
<Modal.ButtonRow>
<Button variant="primary" onClick={onClose}>
OK
</Button>
</Modal.ButtonRow>
</Modal>
);
}

View File

@ -19,7 +19,6 @@ import { Page } from 'app/core/components/Page/Page';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { t, Trans } from 'app/core/internationalization';
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
import { DeleteDashboardButton } from 'app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton';
import { GenAIDashDescriptionButton } from 'app/features/dashboard/components/GenAI/GenAIDashDescriptionButton';
import { GenAIDashTitleButton } from 'app/features/dashboard/components/GenAI/GenAIDashTitleButton';
@ -29,6 +28,7 @@ import { NavToolbarActions } from '../scene/NavToolbarActions';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getDashboardSceneFor } from '../utils/utils';
import { DeleteDashboardButton } from './DeleteDashboardButton';
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
export interface GeneralSettingsEditViewState extends DashboardEditViewState {}
@ -161,9 +161,12 @@ export class GeneralSettingsEditView
this.getCursorSync()?.setState({ sync: value });
};
public onDeleteDashboard = () => {};
static Component = ({ model }: SceneComponentProps<GeneralSettingsEditView>) => {
const { navModel, pageNav } = useDashboardEditPageNav(model.getDashboard(), model.getUrlKey());
const { title, description, tags, meta, editable } = model.getDashboard().useState();
const dashboard = model.getDashboard();
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
const { title, description, tags, meta, editable } = dashboard.useState();
const { sync: graphTooltip } = model.getCursorSync()?.useState() || {};
const { timeZone, weekStart, UNSAFE_nowDelay: nowDelay } = model.getTimeRange().useState();
const { intervals } = model.getRefreshPicker().useState();
@ -172,7 +175,7 @@ export class GeneralSettingsEditView
return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
<NavToolbarActions dashboard={model.getDashboard()} />
<NavToolbarActions dashboard={dashboard} />
<div style={{ maxWidth: '600px' }}>
<Box marginBottom={5}>
<Field
@ -270,7 +273,7 @@ export class GeneralSettingsEditView
</Field>
</CollapsableSection>
<Box marginTop={3}>{meta.canDelete && <DeleteDashboardButton />}</Box>
<Box marginTop={3}>{meta.canDelete && <DeleteDashboardButton dashboard={dashboard} />}</Box>
</div>
</Page>
);