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
pull/92117/head
Laura Benz 9 months ago committed by GitHub
parent 5233e4b47f
commit e7b6b4cf34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      public/app/features/browse-dashboards/api/browseDashboardsAPI.ts
  2. 10
      public/app/features/browse-dashboards/components/PermanentlyDeleteModal.tsx
  3. 15
      public/app/features/browse-dashboards/components/RecentlyDeletedActions.tsx
  4. 53
      public/app/features/browse-dashboards/components/RestoreModal.tsx
  5. 2
      public/locales/en-US/grafana.json
  6. 2
      public/locales/pseudo-LOCALE/grafana.json

@ -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 { useState, useEffect } from 'react';
import { reportInteraction } from '@grafana/runtime';
import { ConfirmModal, Text } from '@grafana/ui';
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}} đäşĥþőäřđ.",

Loading…
Cancel
Save