mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 00:08:10 -05:00
RestoreDashboards: Add folder picker to restore modal (#91945)
* feat: add folder picker * feat: run i18n extraction * feat: add target folder to api query * feat: hand over targetFolder data to api query * feat: retrieve original location * feat: Add condition for folder picker * feat: Modify condition for folder picker default * refactor: add changes from code review * refactor: add changes from code review * feat: add plural to translation, add styling * feat: run i18n extraction
This commit is contained in:
@@ -57,6 +57,7 @@ interface ImportOptions {
|
||||
|
||||
interface RestoreDashboardArgs {
|
||||
dashboardUID: string;
|
||||
targetFolderUID: string;
|
||||
}
|
||||
|
||||
interface HardDeleteDashboardArgs {
|
||||
@@ -394,8 +395,11 @@ export const browseDashboardsAPI = createApi({
|
||||
|
||||
// restore a dashboard that got soft deleted
|
||||
restoreDashboard: builder.mutation<void, RestoreDashboardArgs>({
|
||||
query: ({ dashboardUID }) => ({
|
||||
query: ({ dashboardUID, targetFolderUID }) => ({
|
||||
url: `/dashboards/uid/${dashboardUID}/trash`,
|
||||
data: {
|
||||
folderUid: targetFolderUID,
|
||||
},
|
||||
method: 'PATCH',
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -3,7 +3,13 @@ import { ConfirmModal, Text } from '@grafana/ui';
|
||||
|
||||
import { Trans, t } from '../../../core/internationalization';
|
||||
|
||||
import { Props as ModalProps } from './RestoreModal';
|
||||
interface PermanentlyDeleteModalProps {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
selectedDashboards: string[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const PermanentlyDeleteModal = ({
|
||||
onConfirm,
|
||||
@@ -11,7 +17,7 @@ export const PermanentlyDeleteModal = ({
|
||||
selectedDashboards,
|
||||
isLoading,
|
||||
...props
|
||||
}: ModalProps) => {
|
||||
}: PermanentlyDeleteModalProps) => {
|
||||
const numberOfDashboards = selectedDashboards.length;
|
||||
|
||||
const onDelete = async () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { RestoreModal } from './RestoreModal';
|
||||
export function RecentlyDeletedActions() {
|
||||
const dispatch = useDispatch();
|
||||
const selectedItemsState = useActionSelectionState();
|
||||
const [, stateManager] = useRecentlyDeletedStateManager();
|
||||
const [searchState, stateManager] = useRecentlyDeletedStateManager();
|
||||
|
||||
const [restoreDashboard, { isLoading: isRestoreLoading }] = useRestoreDashboardMutation();
|
||||
const [deleteDashboard, { isLoading: isDeleteLoading }] = useHardDeleteDashboardMutation();
|
||||
@@ -29,20 +29,28 @@ export function RecentlyDeletedActions() {
|
||||
.map(([uid]) => uid);
|
||||
}, [selectedItemsState.dashboard]);
|
||||
|
||||
const dashboardOrigin: Record<string, string> = {};
|
||||
if (searchState.result) {
|
||||
for (const selectedDashboard of selectedDashboards) {
|
||||
const index = searchState.result.view.fields.uid.values.findIndex((e) => e === selectedDashboard);
|
||||
dashboardOrigin[selectedDashboard] = searchState.result.view.fields.location.values[index];
|
||||
}
|
||||
}
|
||||
|
||||
const onActionComplete = () => {
|
||||
dispatch(setAllSelection({ isSelected: false, folderUID: undefined }));
|
||||
|
||||
stateManager.doSearchWithDebounce();
|
||||
};
|
||||
|
||||
const onRestore = async () => {
|
||||
const onRestore = async (restoreTarget: string) => {
|
||||
const resultsView = stateManager.state.result?.view.toArray();
|
||||
if (!resultsView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const promises = selectedDashboards.map((uid) => {
|
||||
return restoreDashboard({ dashboardUID: uid });
|
||||
return restoreDashboard({ dashboardUID: uid, targetFolderUID: restoreTarget });
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
@@ -82,6 +90,7 @@ export function RecentlyDeletedActions() {
|
||||
component: RestoreModal,
|
||||
props: {
|
||||
selectedDashboards,
|
||||
dashboardOrigin,
|
||||
onConfirm: onRestore,
|
||||
isLoading: isRestoreLoading,
|
||||
},
|
||||
|
||||
@@ -1,37 +1,67 @@
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { ConfirmModal, Text } from '@grafana/ui';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { ConfirmModal, Space, Text } from '@grafana/ui';
|
||||
|
||||
import { FolderPicker } from '../../../core/components/Select/FolderPicker';
|
||||
import { Trans, t } from '../../../core/internationalization';
|
||||
|
||||
export interface Props {
|
||||
export interface RestoreModalProps {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => Promise<void>;
|
||||
onConfirm: (restoreTarget: string) => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
selectedDashboards: string[];
|
||||
dashboardOrigin: { [key: string]: string };
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const RestoreModal = ({ onConfirm, onDismiss, selectedDashboards, isLoading, ...props }: Props) => {
|
||||
export const RestoreModal = ({
|
||||
onConfirm,
|
||||
onDismiss,
|
||||
selectedDashboards,
|
||||
dashboardOrigin,
|
||||
isLoading,
|
||||
...props
|
||||
}: RestoreModalProps) => {
|
||||
const [restoreTarget, setRestoreTarget] = useState<string>();
|
||||
const numberOfDashboards = selectedDashboards.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.entries(dashboardOrigin).length === 1 && dashboardOrigin[selectedDashboards[0]] !== 'general') {
|
||||
setRestoreTarget(dashboardOrigin[selectedDashboards[0]]);
|
||||
}
|
||||
}, [dashboardOrigin, selectedDashboards]);
|
||||
|
||||
const onRestore = async () => {
|
||||
reportInteraction('grafana_restore_confirm_clicked', {
|
||||
item_counts: {
|
||||
dashboard: numberOfDashboards,
|
||||
},
|
||||
});
|
||||
await onConfirm();
|
||||
onDismiss();
|
||||
if (restoreTarget !== undefined) {
|
||||
await onConfirm(restoreTarget);
|
||||
onDismiss();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
body={
|
||||
<Text element="p">
|
||||
<Trans i18nKey="recently-deleted.restore-modal.text" count={numberOfDashboards}>
|
||||
This action will restore {{ numberOfDashboards }} dashboards.
|
||||
</Trans>
|
||||
</Text>
|
||||
<>
|
||||
<Text element="p">
|
||||
<Trans i18nKey="recently-deleted.restore-modal.text" count={numberOfDashboards}>
|
||||
This action will restore {{ numberOfDashboards }} dashboards.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Space v={3} />
|
||||
<Text element="p">
|
||||
<Trans i18nKey="recently-deleted.restore-modal.folder-picker-text" count={numberOfDashboards}>
|
||||
Please choose a folder where your dashboards will be restored.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Space v={1} />
|
||||
<FolderPicker onChange={setRestoreTarget} value={restoreTarget} />
|
||||
</>
|
||||
// TODO: replace by list of dashboards (list up to 5 dashboards) or number (from 6 dashboards)?
|
||||
}
|
||||
confirmText={
|
||||
@@ -43,6 +73,7 @@ export const RestoreModal = ({ onConfirm, onDismiss, selectedDashboards, isLoadi
|
||||
onDismiss={onDismiss}
|
||||
onConfirm={onRestore}
|
||||
title={t('recently-deleted.restore-modal.title', 'Restore Dashboards')}
|
||||
disabled={restoreTarget === undefined}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1938,6 +1938,8 @@
|
||||
"title": "Permanently Delete Dashboards"
|
||||
},
|
||||
"restore-modal": {
|
||||
"folder-picker-text_one": "Please choose a folder where your dashboard will be restored.",
|
||||
"folder-picker-text_other": "Please choose a folder where your dashboards will be restored.",
|
||||
"restore-button": "Restore",
|
||||
"restore-loading": "Restoring...",
|
||||
"text_one": "This action will restore {{numberOfDashboards}} dashboard.",
|
||||
|
||||
@@ -1938,6 +1938,8 @@
|
||||
"title": "Pęřmäʼnęʼnŧľy Đęľęŧę Đäşĥþőäřđş"
|
||||
},
|
||||
"restore-modal": {
|
||||
"folder-picker-text_one": "Pľęäşę čĥőőşę ä ƒőľđęř ŵĥęřę yőūř đäşĥþőäřđ ŵįľľ þę řęşŧőřęđ.",
|
||||
"folder-picker-text_other": "Pľęäşę čĥőőşę ä ƒőľđęř ŵĥęřę yőūř đäşĥþőäřđş ŵįľľ þę řęşŧőřęđ.",
|
||||
"restore-button": "Ŗęşŧőřę",
|
||||
"restore-loading": "Ŗęşŧőřįʼnģ...",
|
||||
"text_one": "Ŧĥįş äčŧįőʼn ŵįľľ řęşŧőřę {{numberOfDashboards}} đäşĥþőäřđ.",
|
||||
|
||||
Reference in New Issue
Block a user