Address some minor comments

This commit is contained in:
Roberto Jimenez Sanchez
2025-12-09 13:37:50 +01:00
parent 04282cd931
commit b7b920d728
3 changed files with 39 additions and 30 deletions
@@ -3,6 +3,7 @@ import { useState } from 'react';
import { Trans, t } from '@grafana/i18n';
import { config, reportInteraction } from '@grafana/runtime';
import { Button, Drawer, Stack, Text } from '@grafana/ui';
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1';
import { appEvents } from 'app/core/app_events';
import { ManagerKind } from 'app/features/apiserver/types';
import { BulkDeleteProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource';
@@ -20,7 +21,7 @@ import {
useMoveMultipleFoldersMutationFacade,
} from '../../../../api/clients/folder/v1beta1/hooks';
import { useDeleteDashboardsMutation, useMoveDashboardsMutation } from '../../api/browseDashboardsAPI';
import { useActionSelectionState, useCheckboxSelectionState } from '../../state/hooks';
import { useActionSelectionState } from '../../state/hooks';
import { setAllSelection } from '../../state/slice';
import { DashboardTreeSelection } from '../../types';
@@ -38,8 +39,7 @@ export function BrowseActions({ folderDTO }: Props) {
const [showBulkExportProvisionedResource, setShowBulkExportProvisionedResource] = useState(false);
const dispatch = useDispatch();
const selectedItemsForActions = useActionSelectionState(); // For move/delete - filters out children
const selectedItems = useCheckboxSelectionState(); // For export - includes all selected items
const selectedItems = useActionSelectionState();
const [deleteDashboards] = useDeleteDashboardsMutation();
const deleteFolders = useDeleteMultipleFoldersMutationFacade();
const [moveFolders] = useMoveMultipleFoldersMutationFacade();
@@ -48,10 +48,12 @@ export function BrowseActions({ folderDTO }: Props) {
const provisioningEnabled = config.featureToggles.provisioning;
const { hasProvisioned, hasNonProvisioned } = useSelectionProvisioningStatus(
selectedItemsForActions,
selectedItems,
folderDTO?.managedBy === ManagerKind.Repo
);
const { hasUnmanaged, isLoading: isLoadingUnmanaged } = useSelectionUnmanagedStatus(selectedItems);
const { data: frontendSettings, isLoading: isLoadingSettings } = useGetFrontendSettingsQuery();
const hasRepositories = (frontendSettings?.items?.length ?? 0) > 0;
const isSearching = stateManager.hasSearchFilters();
@@ -65,29 +67,21 @@ export function BrowseActions({ folderDTO }: Props) {
};
const onDelete = async () => {
const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter(
(uid) => selectedItemsForActions.dashboard[uid]
);
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter(
(uid) => selectedItemsForActions.folder[uid]
);
const selectedDashboards = Object.keys(selectedItems.dashboard).filter((uid) => selectedItems.dashboard[uid]);
const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]);
await deleteDashboards({ dashboardUIDs: selectedDashboards });
await deleteFolders({ folderUIDs: selectedFolders });
trackAction('delete', selectedItemsForActions);
trackAction('delete', selectedItems);
onActionComplete();
};
const onMove = async (destinationUID: string) => {
const selectedDashboards = Object.keys(selectedItemsForActions.dashboard).filter(
(uid) => selectedItemsForActions.dashboard[uid]
);
const selectedFolders = Object.keys(selectedItemsForActions.folder).filter(
(uid) => selectedItemsForActions.folder[uid]
);
const selectedDashboards = Object.keys(selectedItems.dashboard).filter((uid) => selectedItems.dashboard[uid]);
const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]);
await moveFolders({ folderUIDs: selectedFolders, destinationUID });
await moveDashboards({ dashboardUIDs: selectedDashboards, destinationUID });
trackAction('move', selectedItemsForActions);
trackAction('move', selectedItems);
onActionComplete();
};
@@ -147,6 +141,11 @@ export function BrowseActions({ folderDTO }: Props) {
}
};
const showExportModal = () => {
trackAction('export', selectedItems);
setShowBulkExportProvisionedResource(true);
};
const moveButton = (
<Button onClick={showMoveModal} variant="secondary">
<Trans i18nKey="browse-dashboards.action.move-button">Move</Trans>
@@ -154,15 +153,14 @@ export function BrowseActions({ folderDTO }: Props) {
);
// 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 = (
const exportButton = (
<Button
onClick={() => setShowBulkExportProvisionedResource(true)}
onClick={showExportModal}
variant="secondary"
disabled={!hasUnmanaged || isLoadingUnmanaged || !hasSelectedDashboards}
disabled={!hasRepositories || isLoadingSettings || !hasUnmanaged || isLoadingUnmanaged || !hasSelectedDashboards}
>
<Trans i18nKey="browse-dashboards.action.export-to-repository-button">Export to Repository</Trans>
</Button>
@@ -172,7 +170,7 @@ export function BrowseActions({ folderDTO }: Props) {
<>
<Stack gap={1} data-testid="manage-actions">
{moveButton}
{provisioningEnabled && pushButton}
{provisioningEnabled && exportButton}
<Button onClick={showDeleteModal} variant="destructive">
<Trans i18nKey="browse-dashboards.action.delete-button">Delete</Trans>
@@ -191,7 +189,7 @@ export function BrowseActions({ folderDTO }: Props) {
size="md"
>
<BulkDeleteProvisionedResource
selectedItems={selectedItemsForActions}
selectedItems={selectedItems}
folderUid={folderDTO?.uid || ''}
onDismiss={() => {
setShowBulkDeleteProvisionedResource(false);
@@ -213,7 +211,7 @@ export function BrowseActions({ folderDTO }: Props) {
size="md"
>
<BulkMoveProvisionedResource
selectedItems={selectedItemsForActions}
selectedItems={selectedItems}
folderUid={folderDTO?.uid}
onDismiss={() => {
setShowBulkMoveProvisionedResource(false);
@@ -237,6 +235,10 @@ export function BrowseActions({ folderDTO }: Props) {
<BulkExportProvisionedResource
selectedItems={selectedItems}
folderUid={folderDTO?.uid}
onActionComplete={() => {
setShowBulkExportProvisionedResource(false);
onActionComplete();
}}
onDismiss={() => {
setShowBulkExportProvisionedResource(false);
}}
@@ -250,6 +252,7 @@ export function BrowseActions({ folderDTO }: Props) {
const actionMap = {
move: 'grafana_manage_dashboards_item_moved',
delete: 'grafana_manage_dashboards_item_deleted',
export: 'grafana_manage_dashboards_item_exported',
} as const;
function trackAction(action: keyof typeof actionMap, selectedItems: Omit<DashboardTreeSelection, 'panel' | '$all'>) {
@@ -4,6 +4,7 @@ import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Button, Drawer, Dropdown, Icon, Menu, MenuItem, Text } from '@grafana/ui';
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1';
import { appEvents } from 'app/core/app_events';
import { Permissions } from 'app/core/components/AccessControl/Permissions';
import { RepoType } from 'app/features/provisioning/Wizard/types';
@@ -38,6 +39,8 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
const [exportSelectedDashboards, setExportSelectedDashboards] = useState<Record<string, boolean>>({});
const [moveFolder] = useMoveFolderMutationFacade();
const isProvisionedInstance = useIsProvisionedInstance();
const { data: frontendSettings } = useGetFrontendSettingsQuery();
const hasRepositories = (frontendSettings?.items?.length ?? 0) > 0;
const deleteFolder = useDeleteFolderMutationFacade();
@@ -160,7 +163,7 @@ export function FolderActionsButton({ folder, repoType, isReadOnlyRepo }: Props)
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 canExportToRepository = config.featureToggles.provisioning && !isProvisionedFolder && hasRepositories;
const menu = (
<Menu>
@@ -4,6 +4,7 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { config, locationService } from '@grafana/runtime';
import { IconName, Menu } from '@grafana/ui';
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1';
import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { DashboardScene } from '../../scene/DashboardScene';
@@ -29,7 +30,9 @@ export function addDashboardExportDrawerItem(item: ExportDrawerMenuItem) {
export default function ExportMenu({ dashboard }: { dashboard: DashboardScene }) {
const provisioningEnabled = config.featureToggles.provisioning;
const isUnmanaged = provisioningEnabled && !dashboard.isManagedRepository();
const { data: frontendSettings } = useGetFrontendSettingsQuery();
const hasRepositories = (frontendSettings?.items?.length ?? 0) > 0;
const canExportToRepository = provisioningEnabled && !dashboard.isManagedRepository() && hasRepositories;
const onMenuItemClick = (shareView: string) => {
locationService.partial({ shareView });
@@ -62,8 +65,8 @@ export default function ExportMenu({ dashboard }: { dashboard: DashboardScene })
onClick: () => onMenuItemClick(shareDashboardType.image),
});
// Add "Export to Repository" option for unmanaged dashboards
if (isUnmanaged) {
// Add "Export to Repository" option for unmanaged dashboards when repositories exist
if (canExportToRepository) {
menuItems.push({
shareId: 'export-to-repository',
testId: 'export-to-repository',
@@ -75,7 +78,7 @@ export default function ExportMenu({ dashboard }: { dashboard: DashboardScene })
}
return menuItems.filter((item) => item.renderCondition);
}, [isUnmanaged]);
}, [canExportToRepository]);
const onClick = (item: ExportDrawerMenuItem) => {
DashboardInteractions.sharingCategoryClicked({