fix: restore export UI components for browse dashboards and dashboard scene
This commit is contained in:
@@ -6,8 +6,10 @@ import { Button, Drawer, Stack, Text } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { ManagerKind } from 'app/features/apiserver/types';
|
||||
import { BulkDeleteProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource';
|
||||
import { BulkExportProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkExportProvisionedResource';
|
||||
import { BulkMoveProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource';
|
||||
import { useSelectionProvisioningStatus } from 'app/features/provisioning/hooks/useSelectionProvisioningStatus';
|
||||
import { useSelectionUnmanagedStatus } from 'app/features/provisioning/hooks/useSelectionUnmanagedStatus';
|
||||
import { useSearchStateManager } from 'app/features/search/state/SearchStateManager';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
import { FolderDTO } from 'app/types/folders';
|
||||
@@ -18,7 +20,7 @@ import {
|
||||
useMoveMultipleFoldersMutationFacade,
|
||||
} from '../../../../api/clients/folder/v1beta1/hooks';
|
||||
import { useDeleteDashboardsMutation, useMoveDashboardsMutation } from '../../api/browseDashboardsAPI';
|
||||
import { useActionSelectionState } from '../../state/hooks';
|
||||
import { useActionSelectionState, useCheckboxSelectionState } from '../../state/hooks';
|
||||
import { setAllSelection } from '../../state/slice';
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
|
||||
@@ -33,9 +35,11 @@ export interface Props {
|
||||
export function BrowseActions({ folderDTO }: Props) {
|
||||
const [showBulkDeleteProvisionedResource, setShowBulkDeleteProvisionedResource] = useState(false);
|
||||
const [showBulkMoveProvisionedResource, setShowBulkMoveProvisionedResource] = useState(false);
|
||||
const [showBulkExportProvisionedResource, setShowBulkExportProvisionedResource] = useState(false);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const selectedItems = useActionSelectionState();
|
||||
const selectedItemsForActions = useActionSelectionState(); // For move/delete - filters out children
|
||||
const selectedItems = useCheckboxSelectionState(); // For export - includes all selected items
|
||||
const [deleteDashboards] = useDeleteDashboardsMutation();
|
||||
const deleteFolders = useDeleteMultipleFoldersMutationFacade();
|
||||
const [moveFolders] = useMoveMultipleFoldersMutationFacade();
|
||||
@@ -44,9 +48,10 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
const provisioningEnabled = config.featureToggles.provisioning;
|
||||
|
||||
const { hasProvisioned, hasNonProvisioned } = useSelectionProvisioningStatus(
|
||||
selectedItems,
|
||||
selectedItemsForActions,
|
||||
folderDTO?.managedBy === ManagerKind.Repo
|
||||
);
|
||||
const { hasUnmanaged, isLoading: isLoadingUnmanaged } = useSelectionUnmanagedStatus(selectedItems);
|
||||
|
||||
const isSearching = stateManager.hasSearchFilters();
|
||||
|
||||
@@ -60,21 +65,29 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
const selectedDashboards = Object.keys(selectedItems.dashboard).filter((uid) => selectedItems.dashboard[uid]);
|
||||
const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]);
|
||||
const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter(
|
||||
(uid) => selectedItemsForActions.dashboard[uid]
|
||||
);
|
||||
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter(
|
||||
(uid) => selectedItemsForActions.folder[uid]
|
||||
);
|
||||
await deleteDashboards({ dashboardUIDs: selectedDashboards });
|
||||
await deleteFolders({ folderUIDs: selectedFolders });
|
||||
trackAction('delete', selectedItems);
|
||||
trackAction('delete', selectedItemsForActions);
|
||||
onActionComplete();
|
||||
};
|
||||
|
||||
const onMove = async (destinationUID: string) => {
|
||||
const selectedDashboards = Object.keys(selectedItems.dashboard).filter((uid) => selectedItems.dashboard[uid]);
|
||||
const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]);
|
||||
const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter(
|
||||
(uid) => selectedItemsForActions.dashboard[uid]
|
||||
);
|
||||
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter(
|
||||
(uid) => selectedItemsForActions.folder[uid]
|
||||
);
|
||||
|
||||
await moveFolders({ folderUIDs: selectedFolders, destinationUID });
|
||||
await moveDashboards({ dashboardUIDs: selectedDashboards, destinationUID });
|
||||
trackAction('move', selectedItems);
|
||||
trackAction('move', selectedItemsForActions);
|
||||
onActionComplete();
|
||||
};
|
||||
|
||||
@@ -140,10 +153,26 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
</Button>
|
||||
);
|
||||
|
||||
// Check if any dashboards are selected (export only supports dashboards, not folders)
|
||||
// Use raw selectedItems (not selectedItemsForActions) to include all selected dashboards
|
||||
const hasSelectedDashboards =
|
||||
Object.keys(selectedItems.dashboard || {}).filter((uid) => selectedItems.dashboard[uid]).length > 0;
|
||||
|
||||
const pushButton = (
|
||||
<Button
|
||||
onClick={() => setShowBulkExportProvisionedResource(true)}
|
||||
variant="secondary"
|
||||
disabled={!hasUnmanaged || isLoadingUnmanaged || !hasSelectedDashboards}
|
||||
>
|
||||
<Trans i18nKey="browse-dashboards.action.export-to-repository-button">Export to Repository</Trans>
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap={1} data-testid="manage-actions">
|
||||
{moveButton}
|
||||
{provisioningEnabled && pushButton}
|
||||
|
||||
<Button onClick={showDeleteModal} variant="destructive">
|
||||
<Trans i18nKey="browse-dashboards.action.delete-button">Delete</Trans>
|
||||
@@ -162,7 +191,7 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
size="md"
|
||||
>
|
||||
<BulkDeleteProvisionedResource
|
||||
selectedItems={selectedItems}
|
||||
selectedItems={selectedItemsForActions}
|
||||
folderUid={folderDTO?.uid || ''}
|
||||
onDismiss={() => {
|
||||
setShowBulkDeleteProvisionedResource(false);
|
||||
@@ -184,7 +213,7 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
size="md"
|
||||
>
|
||||
<BulkMoveProvisionedResource
|
||||
selectedItems={selectedItems}
|
||||
selectedItems={selectedItemsForActions}
|
||||
folderUid={folderDTO?.uid}
|
||||
onDismiss={() => {
|
||||
setShowBulkMoveProvisionedResource(false);
|
||||
@@ -192,6 +221,28 @@ export function BrowseActions({ folderDTO }: Props) {
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
|
||||
{/* bulk export */}
|
||||
{showBulkExportProvisionedResource && (
|
||||
<Drawer
|
||||
title={
|
||||
// Heading levels should only increase by one (a11y)
|
||||
<Text variant="h3" element="h2">
|
||||
{t('browse-dashboards.action.export-provisioned-resources', 'Export Resources')}
|
||||
</Text>
|
||||
}
|
||||
onClose={() => setShowBulkExportProvisionedResource(false)}
|
||||
size="md"
|
||||
>
|
||||
<BulkExportProvisionedResource
|
||||
selectedItems={selectedItems}
|
||||
folderUid={folderDTO?.uid}
|
||||
onDismiss={() => {
|
||||
setShowBulkExportProvisionedResource(false);
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DashboardViewItem } from 'app/features/search/types';
|
||||
import { useDispatch, useSelector } from 'app/types/store';
|
||||
|
||||
import { PAGE_SIZE } from '../api/services';
|
||||
import { fetchNextChildrenPage } from '../state/actions';
|
||||
import { fetchNextChildrenPage, selectFolderWithAllDashboards } from '../state/actions';
|
||||
import {
|
||||
useFlatTreeState,
|
||||
useCheckboxSelectionState,
|
||||
@@ -81,7 +81,13 @@ export function BrowseView({ folderUID, width, height, permissions, isReadOnlyRe
|
||||
|
||||
const handleItemSelectionChange = useCallback(
|
||||
(item: DashboardViewItem, isSelected: boolean) => {
|
||||
dispatch(setItemSelectionState({ item, isSelected }));
|
||||
// If selecting a folder, use the async thunk to collect all dashboards recursively
|
||||
// When deselecting, the normal reducer will handle deselecting all children
|
||||
if (item.kind === 'folder') {
|
||||
dispatch(selectFolderWithAllDashboards({ folderUID: item.uid, isSelected }));
|
||||
} else {
|
||||
dispatch(setItemSelectionState({ item, isSelected }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
@@ -2,14 +2,16 @@ import { useState } from 'react';
|
||||
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { config, locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Drawer, Dropdown, Icon, Menu, MenuItem, Text } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { Permissions } from 'app/core/components/AccessControl/Permissions';
|
||||
import { RepoType } from 'app/features/provisioning/Wizard/types';
|
||||
import { BulkExportProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkExportProvisionedResource';
|
||||
import { BulkMoveProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource';
|
||||
import { DeleteProvisionedFolderForm } from 'app/features/provisioning/components/Folders/DeleteProvisionedFolderForm';
|
||||
import { useIsProvisionedInstance } from 'app/features/provisioning/hooks/useIsProvisionedInstance';
|
||||
import { collectAllDashboardsUnderFolder } from 'app/features/provisioning/utils/collectFolderDashboards';
|
||||
import { getReadOnlyTooltipText } from 'app/features/provisioning/utils/repository';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
import { FolderDTO } from 'app/types/folders';
|
||||
@@ -32,6 +34,8 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
|
||||
const [showPermissionsDrawer, setShowPermissionsDrawer] = useState(false);
|
||||
const [showDeleteProvisionedFolderDrawer, setShowDeleteProvisionedFolderDrawer] = useState(false);
|
||||
const [showMoveProvisionedFolderDrawer, setShowMoveProvisionedFolderDrawer] = useState(false);
|
||||
const [showExportFolderDrawer, setShowExportFolderDrawer] = useState(false);
|
||||
const [exportSelectedDashboards, setExportSelectedDashboards] = useState<Record<string, boolean>>({});
|
||||
const [moveFolder] = useMoveFolderMutationFacade();
|
||||
const isProvisionedInstance = useIsProvisionedInstance();
|
||||
|
||||
@@ -125,9 +129,38 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
|
||||
setShowMoveProvisionedFolderDrawer(true);
|
||||
};
|
||||
|
||||
const handleExportFolder = async () => {
|
||||
try {
|
||||
// Collect all dashboards under this folder and its children
|
||||
const dashboardUIDs = await collectAllDashboardsUnderFolder(folder.uid);
|
||||
|
||||
// Create selected items object with all dashboards
|
||||
const selectedDashboards: Record<string, boolean> = {};
|
||||
dashboardUIDs.forEach((uid) => {
|
||||
selectedDashboards[uid] = true;
|
||||
});
|
||||
|
||||
setExportSelectedDashboards(selectedDashboards);
|
||||
setShowExportFolderDrawer(true);
|
||||
} catch (error) {
|
||||
appEvents.publish({
|
||||
type: AppEvents.alertError.name,
|
||||
payload: [
|
||||
t(
|
||||
'browse-dashboards.folder-actions-button.export-folder-error',
|
||||
'Error collecting dashboards. Please try again later.'
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const managePermissionsLabel = t('browse-dashboards.folder-actions-button.manage-permissions', 'Manage permissions');
|
||||
const moveLabel = t('browse-dashboards.folder-actions-button.move', 'Move this folder');
|
||||
const deleteLabel = t('browse-dashboards.folder-actions-button.delete', 'Delete this folder');
|
||||
const exportLabel = t('browse-dashboards.folder-actions-button.export', 'Export to Repository');
|
||||
|
||||
const canExportToRepository = config.featureToggles.provisioning && !isProvisionedFolder;
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
@@ -147,10 +180,18 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
|
||||
label={deleteLabel}
|
||||
/>
|
||||
)}
|
||||
{canExportToRepository && <MenuItem onClick={handleExportFolder} label={exportLabel} />}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
if (!canViewPermissions && !canMoveFolder && !canDeleteFolders) {
|
||||
// Show menu if there are any available actions
|
||||
const hasAnyActions =
|
||||
(canViewPermissions && !isProvisionedFolder) ||
|
||||
(canMoveFolder && !isReadOnlyRepo) ||
|
||||
(canDeleteFolders && !isReadOnlyRepo) ||
|
||||
canExportToRepository;
|
||||
|
||||
if (!hasAnyActions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -213,6 +254,30 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
{showExportFolderDrawer && (
|
||||
<Drawer
|
||||
title={
|
||||
<Text variant="h3" element="h2">
|
||||
{t('browse-dashboards.action.export-folder', 'Export Folder to Repository')}
|
||||
</Text>
|
||||
}
|
||||
subtitle={folder.title}
|
||||
onClose={() => setShowExportFolderDrawer(false)}
|
||||
size="md"
|
||||
>
|
||||
<BulkExportProvisionedResource
|
||||
folderUid={folder.uid}
|
||||
selectedItems={{
|
||||
dashboard: exportSelectedDashboards,
|
||||
folder: {},
|
||||
}}
|
||||
onDismiss={() => {
|
||||
setShowExportFolderDrawer(false);
|
||||
setExportSelectedDashboards({});
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createAsyncThunk } from 'app/types/store';
|
||||
import { listDashboards, listFolders, PAGE_SIZE } from '../api/services';
|
||||
import { DashboardViewItemWithUIItems, UIDashboardViewItem } from '../types';
|
||||
|
||||
import { setItemSelectionState } from './slice';
|
||||
import { findItem } from './utils';
|
||||
|
||||
interface FetchNextChildrenPageArgs {
|
||||
@@ -88,6 +89,69 @@ export const refetchChildren = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
export const selectFolderWithAllDashboards = createAsyncThunk(
|
||||
'browseDashboards/selectFolderWithAllDashboards',
|
||||
async ({ folderUID, isSelected }: { folderUID: string; isSelected: boolean }, { dispatch, getState }) => {
|
||||
const state = getState().browseDashboards;
|
||||
|
||||
// Find the folder item to get its parentUID and managedBy
|
||||
const folderItem = findItem(state.rootItems?.items ?? [], state.childrenByParentUID, folderUID);
|
||||
|
||||
if (!isSelected) {
|
||||
// When deselecting, use the normal action - it will handle deselecting all children recursively
|
||||
dispatch(
|
||||
setItemSelectionState({
|
||||
item: {
|
||||
kind: 'folder',
|
||||
uid: folderUID,
|
||||
parentUID: folderItem?.parentUID,
|
||||
managedBy: folderItem?.managedBy,
|
||||
},
|
||||
isSelected: false,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// When selecting, collect all dashboards recursively
|
||||
const { collectAllDashboardsUnderFolder } = await import('app/features/provisioning/utils/collectFolderDashboards');
|
||||
const dashboardUIDs = await collectAllDashboardsUnderFolder(folderUID);
|
||||
|
||||
// First, select the folder itself
|
||||
dispatch(
|
||||
setItemSelectionState({
|
||||
item: {
|
||||
kind: 'folder',
|
||||
uid: folderUID,
|
||||
parentUID: folderItem?.parentUID,
|
||||
managedBy: folderItem?.managedBy,
|
||||
},
|
||||
isSelected: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Then select all dashboards found
|
||||
// We need to get the parentUID for each dashboard from the state
|
||||
// If a dashboard isn't in state yet, we still need to select it
|
||||
for (const dashboardUID of dashboardUIDs) {
|
||||
const dashboardItem = findItem(state.rootItems?.items ?? [], state.childrenByParentUID, dashboardUID);
|
||||
// Even if dashboard isn't in state, we can still select it by UID
|
||||
// The reducer will handle setting selectedItems.dashboard[dashboardUID] = true
|
||||
dispatch(
|
||||
setItemSelectionState({
|
||||
item: {
|
||||
kind: 'dashboard',
|
||||
uid: dashboardUID,
|
||||
parentUID: dashboardItem?.parentUID ?? folderUID, // Fallback to folderUID if not found
|
||||
managedBy: dashboardItem?.managedBy,
|
||||
},
|
||||
isSelected: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchNextChildrenPage = createAsyncThunk(
|
||||
'browseDashboards/fetchNextChildrenPage',
|
||||
async (
|
||||
|
||||
@@ -80,9 +80,8 @@ export function DeleteDashboardModal({ dashboardTitle, onConfirm, onClose }: Del
|
||||
<>
|
||||
<Text element="p">
|
||||
<Trans i18nKey="dashboard-settings.delete-modal-restore-dashboards-text">
|
||||
This action will delete the dashboard. Deleted dashboards will be kept in the history for up to 12
|
||||
months and can be restored by your organization administrator during that time. The history is limited
|
||||
to 1000 dashboards—older ones will be removed sooner if the limit is reached.
|
||||
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} />
|
||||
|
||||
@@ -28,6 +28,9 @@ export function addDashboardExportDrawerItem(item: ExportDrawerMenuItem) {
|
||||
}
|
||||
|
||||
export default function ExportMenu({ dashboard }: { dashboard: DashboardScene }) {
|
||||
const provisioningEnabled = config.featureToggles.provisioning;
|
||||
const isUnmanaged = provisioningEnabled && !dashboard.isManagedRepository();
|
||||
|
||||
const onMenuItemClick = (shareView: string) => {
|
||||
locationService.partial({ shareView });
|
||||
};
|
||||
@@ -59,8 +62,20 @@ export default function ExportMenu({ dashboard }: { dashboard: DashboardScene })
|
||||
onClick: () => onMenuItemClick(shareDashboardType.image),
|
||||
});
|
||||
|
||||
// Add "Export to Repository" option for unmanaged dashboards
|
||||
if (isUnmanaged) {
|
||||
menuItems.push({
|
||||
shareId: 'export-to-repository',
|
||||
testId: 'export-to-repository',
|
||||
icon: 'cloud-upload',
|
||||
label: t('share-dashboard.menu.export-to-repository-title', 'Export to Repository'),
|
||||
renderCondition: true,
|
||||
onClick: () => onMenuItemClick('export-to-repository'),
|
||||
});
|
||||
}
|
||||
|
||||
return menuItems.filter((item) => item.renderCondition);
|
||||
}, []);
|
||||
}, [isUnmanaged]);
|
||||
|
||||
const onClick = (item: ExportDrawerMenuItem) => {
|
||||
DashboardInteractions.sharingCategoryClicked({
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DashboardScene } from '../../scene/DashboardScene';
|
||||
import { getDashboardSceneFor } from '../../utils/utils';
|
||||
import { ExportAsCode } from '../ExportButton/ExportAsCode';
|
||||
import { ExportAsImage } from '../ExportButton/ExportAsImage';
|
||||
import { ExportToRepository } from '../ExportButton/ExportToRepository';
|
||||
import { ShareExternally } from '../ShareButton/share-externally/ShareExternally';
|
||||
import { ShareInternally } from '../ShareButton/share-internally/ShareInternally';
|
||||
import { ShareSnapshot } from '../ShareButton/share-snapshot/ShareSnapshot';
|
||||
@@ -96,6 +97,8 @@ function getShareView(
|
||||
return new ExportAsCode({ onDismiss });
|
||||
case shareDashboardType.image:
|
||||
return new ExportAsImage({ onDismiss });
|
||||
case 'export-to-repository':
|
||||
return new ExportToRepository({ onDismiss });
|
||||
default:
|
||||
return new ShareInternally({ onDismiss });
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { saveAs } from 'file-saver';
|
||||
import { memo, useState, useMemo } from 'react';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Field, Modal, Switch } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, Drawer, Field, Modal, Switch, Text } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal/DashboardExporter';
|
||||
import { makeExportableV1 } from 'app/features/dashboard-scene/scene/export/exporters';
|
||||
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
|
||||
import { BulkExportProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkExportProvisionedResource';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
import { ViewJsonModal } from './ViewJsonModal';
|
||||
@@ -17,7 +19,10 @@ interface Props extends ShareModalTabProps {}
|
||||
|
||||
export const ShareExport = memo(({ dashboard, panel, onDismiss }: Props) => {
|
||||
const [shareExternally, setShareExternally] = useState(false);
|
||||
const [showExportToRepositoryDrawer, setShowExportToRepositoryDrawer] = useState(false);
|
||||
const exporter = useMemo(() => new DashboardExporter(), []);
|
||||
const provisioningEnabled = config.featureToggles.provisioning;
|
||||
const isUnmanaged = !dashboard.meta.provisioned;
|
||||
|
||||
const onShareExternallyChange = () => setShareExternally((prev) => !prev);
|
||||
|
||||
@@ -29,6 +34,10 @@ export const ShareExport = memo(({ dashboard, panel, onDismiss }: Props) => {
|
||||
|
||||
if (shareExternally) {
|
||||
makeExportableV1(dashboard).then((dashboardJson) => {
|
||||
if ('error' in dashboardJson) {
|
||||
console.error('Failed to export dashboard:', dashboardJson.error);
|
||||
return;
|
||||
}
|
||||
openSaveAsDialog(dashboardJson);
|
||||
});
|
||||
} else {
|
||||
@@ -51,13 +60,17 @@ export const ShareExport = memo(({ dashboard, panel, onDismiss }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const openSaveAsDialog = (dash: any) => {
|
||||
const openSaveAsDialog = (dash: unknown) => {
|
||||
const dashboardJsonPretty = JSON.stringify(dash, null, 2);
|
||||
const blob = new Blob([dashboardJsonPretty], {
|
||||
type: 'application/json;charset=utf-8',
|
||||
});
|
||||
const time = new Date().getTime();
|
||||
saveAs(blob, `${dash.title}-${time}.json`);
|
||||
const title =
|
||||
typeof dash === 'object' && dash !== null && 'title' in dash && typeof dash.title === 'string'
|
||||
? dash.title
|
||||
: 'dashboard';
|
||||
saveAs(blob, `${title}-${time}.json`);
|
||||
};
|
||||
|
||||
const openJsonModal = (clone: object) => {
|
||||
@@ -80,13 +93,18 @@ export const ShareExport = memo(({ dashboard, panel, onDismiss }: Props) => {
|
||||
<p>
|
||||
<Trans i18nKey="share-modal.export.info-text">Export this dashboard.</Trans>
|
||||
</p>
|
||||
<Field label={exportExternallyTranslation}>
|
||||
<Field label={exportExternallyTranslation} noMargin>
|
||||
<Switch id="share-externally-toggle" value={shareExternally} onChange={onShareExternallyChange} />
|
||||
</Field>
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" onClick={onDismiss} fill="outline">
|
||||
<Trans i18nKey="share-modal.export.cancel-button">Cancel</Trans>
|
||||
</Button>
|
||||
{provisioningEnabled && isUnmanaged && (
|
||||
<Button variant="secondary" onClick={() => setShowExportToRepositoryDrawer(true)}>
|
||||
<Trans i18nKey="share-modal.export.export-to-repository-button">Export to Repository</Trans>
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="secondary" onClick={onViewJson}>
|
||||
<Trans i18nKey="share-modal.export.view-button">View JSON</Trans>
|
||||
</Button>
|
||||
@@ -94,6 +112,30 @@ export const ShareExport = memo(({ dashboard, panel, onDismiss }: Props) => {
|
||||
<Trans i18nKey="share-modal.export.save-button">Save to file</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
{showExportToRepositoryDrawer && (
|
||||
<Drawer
|
||||
title={
|
||||
<Text variant="h3" element="h2">
|
||||
{t('share-modal.export.export-to-repository-title', 'Export Dashboard to Repository')}
|
||||
</Text>
|
||||
}
|
||||
subtitle={dashboard.title}
|
||||
onClose={() => setShowExportToRepositoryDrawer(false)}
|
||||
size="md"
|
||||
>
|
||||
<BulkExportProvisionedResource
|
||||
folderUid={dashboard.meta.folderUid}
|
||||
selectedItems={{
|
||||
dashboard: dashboard.uid ? { [dashboard.uid]: true } : {},
|
||||
folder: {},
|
||||
}}
|
||||
onDismiss={() => {
|
||||
setShowExportToRepositoryDrawer(false);
|
||||
onDismiss?.();
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user