Scenes/RestoreDashboards: Adjust DeleteDashboardModal and Toast message (#92601)

* refactor: replace soft delete method

* refactor: adjust test to new naming

* refactor: clean up unused functions

* refactor: replace delete modal

* refactor: use new delete modal outside of scenes
This commit is contained in:
Laura Benz
2024-09-02 18:04:02 +02:00
committed by GitHub
parent a1190b165b
commit cdf035f813
8 changed files with 79 additions and 115 deletions

View File

@@ -2886,8 +2886,7 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
],
"public/app/features/dashboard-scene/settings/JsonModelEditView.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
@@ -4679,12 +4678,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
[0, 0, 0, "Unexpected any. Specify a different type.", "12"],
[0, 0, 0, "Unexpected any. Specify a different type.", "13"]
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
],
"public/app/features/manage-dashboards/state/reducers.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],

View File

@@ -1000,7 +1000,7 @@ describe('DashboardScene', () => {
scene.setState({ isDirty: true });
locationService.push('/d/adsdas');
await scene.deleteDashboard();
await scene.onDashboardDelete();
expect(scene.state.isDirty).toBe(false);
});

View File

@@ -34,7 +34,6 @@ 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 { getClosestScopesFacade, ScopesFacade } from 'app/features/scopes';
import { VariablesChanged } from 'app/features/variables/types';
import { DashboardDTO, DashboardMeta, KioskMode, SaveDashboardResponseDTO } from 'app/types';
@@ -891,8 +890,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
this._initialSaveModel = saveModel;
}
public async deleteDashboard() {
await deleteDashboard(this.state.uid!, true);
public async onDashboardDelete() {
// Need to mark it non dirty to navigate away without unsaved changes warning
this.setState({ isDirty: false });
locationService.replace('/');

View File

@@ -2,17 +2,56 @@ import { useAsyncFn, useToggle } from 'react-use';
import { selectors } from '@grafana/e2e-selectors';
import { config, reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal, Modal } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Button, ConfirmModal, Modal, Space, Text } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import { useDeleteItemsMutation } from '../../browse-dashboards/api/browseDashboardsAPI';
import { DashboardScene } from '../scene/DashboardScene';
interface ButtonProps {
dashboard: DashboardScene;
}
interface ProvisionedDeleteModalProps {
dashboardId: string | undefined;
onClose: () => void;
}
interface DeleteModalProps {
dashboardTitle: string;
onConfirm: () => void;
onClose: () => void;
}
export function DeleteDashboardButton({ dashboard }: ButtonProps) {
const [showModal, toggleModal] = useToggle(false);
const [deleteItems] = useDeleteItemsMutation();
const [, onConfirm] = useAsyncFn(async () => {
reportInteraction('grafana_manage_dashboards_delete_clicked', {
item_counts: {
dashboard: 1,
},
source: 'dashboard_scene_settings',
restore_enabled: config.featureToggles.dashboardRestoreUI,
});
toggleModal();
if (dashboard.state.uid) {
await deleteItems({
selectedItems: {
dashboard: {
[dashboard.state.uid]: true,
},
folder: {},
},
});
}
await dashboard.onDashboardDelete();
}, [dashboard, toggleModal]);
if (dashboard.state.meta.provisioned && showModal) {
return <ProvisionedDeleteModal dashboardId={dashboard.state.meta.provisionedExternalId} onClose={toggleModal} />;
}
return (
<>
@@ -24,52 +63,48 @@ export function DeleteDashboardButton({ dashboard }: ButtonProps) {
<Trans i18nKey="dashboard-settings.dashboard-delete-button">Delete dashboard</Trans>
</Button>
{showModal && <DeleteDashboardModal dashboard={dashboard} onClose={toggleModal} />}
{showModal && (
<DeleteDashboardModal dashboardTitle={dashboard.state.title} onConfirm={onConfirm} onClose={toggleModal} />
)}
</>
);
}
interface ModalProps {
dashboard: DashboardScene;
onClose: () => void;
}
function DeleteDashboardModal({ dashboard, onClose }: ModalProps) {
const [, onConfirm] = useAsyncFn(async () => {
reportInteraction('grafana_manage_dashboards_delete_clicked', {
item_counts: {
dashboard: 1,
},
source: 'dashboard_scene_settings',
restore_enabled: config.featureToggles.dashboardRestoreUI,
});
onClose();
await dashboard.deleteDashboard();
}, [dashboard, onClose]);
if (dashboard.state.meta.provisioned) {
return <ProvisionedDeleteModal dashboard={dashboard} onClose={onClose} />;
}
export function DeleteDashboardModal({ dashboardTitle, onConfirm, onClose }: DeleteModalProps) {
return (
<ConfirmModal
isOpen={true}
body={
<>
<p>Do you want to delete this dashboard?</p>
<p>{dashboard.state.title}</p>
{config.featureToggles.dashboardRestore && (
<>
<Text element="p">
<Trans i18nKey="dashboard-settings.delete-modal-restore-dashboards-text">
This action will mark the dashboard for deletion in 30 days. Your organization administrator can
restore it anytime before the 30 days expire.
</Trans>
</Text>
<Space v={1} />
</>
)}
<Text element="p">
<Trans i18nKey="dashboard-settings.delete-modal-text">Do you want to delete this dashboard?</Trans>
</Text>
<Text element="p">{dashboardTitle}</Text>
<Space v={2} />
</>
}
onConfirm={onConfirm}
onDismiss={onClose}
title="Delete"
title={t('dashboard-settings.delete-modal.title', 'Delete')}
icon="trash-alt"
confirmText="Delete"
confirmText={t('dashboard-settings.delete-modal.delete-button', 'Delete')}
confirmationText={t('dashboard-settings.delete-modal.confirmation-text', 'Delete')}
/>
);
}
function ProvisionedDeleteModal({ dashboard, onClose }: ModalProps) {
function ProvisionedDeleteModal({ dashboardId, onClose }: ProvisionedDeleteModalProps) {
return (
<Modal isOpen={true} title="Cannot delete provisioned dashboard" icon="trash-alt" onDismiss={onClose}>
<p>
@@ -90,7 +125,7 @@ function ProvisionedDeleteModal({ dashboard, onClose }: ModalProps) {
for more information about provisioning.
</i>
<br />
File path: {dashboard.state.meta.provisionedExternalId}
File path: {dashboardId}
</p>
<Modal.ButtonRow>
<Button variant="primary" onClick={onClose}>

View File

@@ -3,12 +3,13 @@ import { connect, ConnectedProps } from 'react-redux';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { locationService, config, reportInteraction } from '@grafana/runtime';
import { Modal, ConfirmModal, Button, Text, Space, TextLink } from '@grafana/ui';
import { Modal, Button, Text, Space, TextLink } from '@grafana/ui';
import { DashboardModel } from 'app/features/dashboard/state';
import { cleanUpDashboardAndVariables } from 'app/features/dashboard/state/actions';
import { Trans, t } from '../../../../core/internationalization';
import { useDeleteItemsMutation } from '../../../browse-dashboards/api/browseDashboardsAPI';
import { DeleteDashboardModal as DeleteModal } from '../../../dashboard-scene/settings/DeleteDashboardButton';
type DeleteDashboardModalProps = {
hideModal(): void;
@@ -52,29 +53,7 @@ const DeleteDashboardModalUnconnected = ({ hideModal, cleanUpDashboardAndVariabl
return <ProvisionedDeleteModal hideModal={hideModal} provisionedId={dashboard.meta.provisionedExternalId!} />;
}
return (
<ConfirmModal
isOpen={true}
body={
<>
<Text element="p">
<Trans i18nKey="dashboard-settings.dashboard-delete-modal.text">
Do you want to delete this dashboard?
</Trans>
</Text>
<Space v={1} />
<Text element="p">{dashboard.title}</Text>
<Space v={2} />
</>
}
onConfirm={onConfirm}
onDismiss={hideModal}
title={t('dashboard-settings.dashboard-delete-modal.title', 'Delete')}
icon="trash-alt"
confirmText={t('dashboard-settings.dashboard-delete-modal.delete-button', 'Delete')}
confirmationText={t('dashboard-settings.dashboard-delete-modal.confirmation-text', 'Delete')}
/>
);
return <DeleteModal onConfirm={onConfirm} onClose={hideModal} dashboardTitle={dashboard.title} />;
};
const ProvisionedDeleteModal = ({ hideModal, provisionedId }: { hideModal(): void; provisionedId: string }) => (

View File

@@ -3,7 +3,6 @@ import { getBackendSrv, getDataSourceSrv, isFetchError } from '@grafana/runtime'
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { browseDashboardsAPI, ImportInputs } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
import { FolderInfo, PermissionLevelString, SearchQueryType, ThunkResult } from 'app/types';
import {
@@ -281,39 +280,6 @@ export async function moveFolders(folderUIDs: string[], toFolder: FolderInfo) {
return result;
}
function createTask(fn: (...args: any[]) => Promise<any>, ignoreRejections: boolean, ...args: any[]) {
return async (result: any) => {
try {
const res = await fn(...args);
return Array.prototype.concat(result, [res]);
} catch (err) {
if (ignoreRejections) {
return result;
}
throw err;
}
};
}
export function deleteFoldersAndDashboards(folderUids: string[], dashboardUids: string[]) {
const tasks = [];
for (const folderUid of folderUids) {
tasks.push(createTask(deleteFolder, true, folderUid, true));
}
for (const dashboardUid of dashboardUids) {
tasks.push(createTask(deleteDashboard, true, dashboardUid, true));
}
return executeInOrder(tasks);
}
function deleteFolder(uid: string, showSuccessAlert: boolean) {
return getBackendSrv().delete(`/api/folders/${uid}?forceDeleteRules=false`, undefined, { showSuccessAlert });
}
export function createFolder(payload: any) {
return getBackendSrv().post('/api/folders', payload);
}
@@ -346,13 +312,3 @@ export function getFolderByUid(uid: string): Promise<{ uid: string; title: strin
export function getFolderById(id: number): Promise<{ id: number; title: string }> {
return getBackendSrv().get(`/api/folders/id/${id}`);
}
export function deleteDashboard(uid: string, showSuccessAlert: boolean) {
return getDashboardAPI().deleteDashboard(uid, showSuccessAlert);
}
function executeInOrder(tasks: any[]): Promise<unknown> {
return tasks.reduce((acc, task) => {
return Promise.resolve(acc).then(task);
}, []);
}

View File

@@ -607,12 +607,13 @@
"title": "Annotations"
},
"dashboard-delete-button": "Delete dashboard",
"dashboard-delete-modal": {
"delete-modal": {
"confirmation-text": "Delete",
"delete-button": "Delete",
"text": "Do you want to delete this dashboard?",
"title": "Delete"
},
"delete-modal-restore-dashboards-text": "This action will mark the dashboard for deletion in 30 days. Your organization administrator can restore it anytime before the 30 days expire.",
"delete-modal-text": "Do you want to delete this dashboard?",
"general": {
"auto-refresh-description": "Define the auto refresh intervals that should be available in the auto refresh list. Use the format '5s' for seconds, '1m' for minutes, '1h' for hours, and '1d' for days (e.g.: '5s,10s,30s,1m,5m,15m,30m,1h,2h,1d').",
"auto-refresh-label": "Auto refresh",

View File

@@ -607,12 +607,13 @@
"title": "Åʼnʼnőŧäŧįőʼnş"
},
"dashboard-delete-button": "Đęľęŧę đäşĥþőäřđ",
"dashboard-delete-modal": {
"delete-modal": {
"confirmation-text": "Đęľęŧę",
"delete-button": "Đęľęŧę",
"text": "Đő yőū ŵäʼnŧ ŧő đęľęŧę ŧĥįş đäşĥþőäřđ?",
"title": "Đęľęŧę"
},
"delete-modal-restore-dashboards-text": "Ŧĥįş äčŧįőʼn ŵįľľ mäřĸ ŧĥę đäşĥþőäřđ ƒőř đęľęŧįőʼn įʼn 30 đäyş. Ÿőūř őřģäʼnįžäŧįőʼn äđmįʼnįşŧřäŧőř čäʼn řęşŧőřę įŧ äʼnyŧįmę þęƒőřę ŧĥę 30 đäyş ęχpįřę.",
"delete-modal-text": "Đő yőū ŵäʼnŧ ŧő đęľęŧę ŧĥįş đäşĥþőäřđ?",
"general": {
"auto-refresh-description": "Đęƒįʼnę ŧĥę äūŧő řęƒřęşĥ įʼnŧęřväľş ŧĥäŧ şĥőūľđ þę äväįľäþľę įʼn ŧĥę äūŧő řęƒřęşĥ ľįşŧ. Ůşę ŧĥę ƒőřmäŧ '5ş' ƒőř şęčőʼnđş, '1m' ƒőř mįʼnūŧęş, '1ĥ' ƒőř ĥőūřş, äʼnđ '1đ' ƒőř đäyş (ę.ģ.: '5ş,10ş,30ş,1m,5m,15m,30m,1ĥ,2ĥ,1đ').",
"auto-refresh-label": "Åūŧő řęƒřęşĥ",