diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 857a54c8360..073e7a51946 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -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({ - query: ({ dashboardUID }) => ({ + query: ({ dashboardUID, targetFolderUID }) => ({ url: `/dashboards/uid/${dashboardUID}/trash`, + data: { + folderUid: targetFolderUID, + }, method: 'PATCH', }), }), diff --git a/public/app/features/browse-dashboards/components/PermanentlyDeleteModal.tsx b/public/app/features/browse-dashboards/components/PermanentlyDeleteModal.tsx index b14d308eaad..25e8a8d13e5 100644 --- a/public/app/features/browse-dashboards/components/PermanentlyDeleteModal.tsx +++ b/public/app/features/browse-dashboards/components/PermanentlyDeleteModal.tsx @@ -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; + 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 () => { diff --git a/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx b/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx index f199544539f..8df52a18f84 100644 --- a/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx @@ -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 = {}; + 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, }, diff --git a/public/app/features/browse-dashboards/components/RestoreModal.tsx b/public/app/features/browse-dashboards/components/RestoreModal.tsx index 22ac1f97c95..e582370a773 100644 --- a/public/app/features/browse-dashboards/components/RestoreModal.tsx +++ b/public/app/features/browse-dashboards/components/RestoreModal.tsx @@ -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; + onConfirm: (restoreTarget: string) => Promise; 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(); 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 ( - - This action will restore {{ numberOfDashboards }} dashboards. - - + <> + + + This action will restore {{ numberOfDashboards }} dashboards. + + + + + + Please choose a folder where your dashboards will be restored. + + + + + // 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} /> ); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index eab5ecd0bb6..7858779fd21 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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.", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index c0b22d9ccbf..deafbde58f3 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -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}} đäşĥþőäřđ.",