From b48131b6bfcc3bf876c0cf80783bb08514047017 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Thu, 18 Sep 2025 11:09:00 -0400 Subject: [PATCH] BulkActionError: Add Git Sync Error Display for Bulk Delete and Bulk Move (#111195) * BulkDeleteProvisionedResource & BulkMoveProvisionedResource: display error * use ProvisioningAlert component * MoveProvisionedDashboardForm use ProvisioningAlert * DeleteProvisionedDashboardForm use ProvisioningAlert --- .../BulkDeleteProvisionedResource.tsx | 30 ++++++++---- .../BulkMoveProvisionedResource.tsx | 30 ++++++++---- .../DeleteProvisionedDashboardForm.tsx | 48 ++++++++++++------- .../MoveProvisionedDashboardForm.tsx | 31 ++++++++---- 4 files changed, 97 insertions(+), 42 deletions(-) diff --git a/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx b/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx index ba7d07bef6b..5c0c6f5de32 100644 --- a/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx +++ b/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { AppEvents } from '@grafana/data'; @@ -12,7 +12,10 @@ import { JobStatus } from 'app/features/provisioning/Job/JobStatus'; import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView'; import { GENERAL_FOLDER_UID } from 'app/features/search/constants'; +import { ProvisioningAlert } from '../../Shared/ProvisioningAlert'; +import { StepStatusInfo } from '../../Wizard/types'; import { useSelectionRepoValidation } from '../../hooks/useSelectionRepoValidation'; +import { StatusInfo } from '../../types'; import { RepoInvalidStateBanner } from '../Shared/RepoInvalidStateBanner'; import { ResourceEditFormSharedFields } from '../Shared/ResourceEditFormSharedFields'; import { getDefaultWorkflow, getWorkflowOptions } from '../defaults'; @@ -30,6 +33,7 @@ interface FormProps extends BulkActionProvisionResourceProps { function FormContent({ initialValues, selectedItems, repository, workflowOptions, onDismiss }: FormProps) { // States const [job, setJob] = useState(); + const [jobError, setJobError] = useState(); const [hasSubmitted, setHasSubmitted] = useState(false); // Hooks @@ -72,21 +76,29 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions const disableBtn = isCreatingJob || job?.status?.state === 'working' || job?.status?.state === 'pending' || hasSubmitted; + const onStatusChange = useCallback((statusInfo: StepStatusInfo) => { + if (statusInfo.status === 'error' && statusInfo.error) { + setJobError(statusInfo.error); + } + }, []); + return (
- - - This will delete selected folders and their descendants. In total, this will affect: - - - - {hasSubmitted && job ? ( - + <> + + + ) : ( <> + + + This will delete selected folders and their descendants. In total, this will affect: + + + (); + const [jobError, setJobError] = useState(); const [targetFolderUID, setTargetFolderUID] = useState(undefined); const [hasSubmitted, setHasSubmitted] = useState(false); @@ -109,20 +113,30 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions } }; + const onStatusChange = useCallback((statusInfo: StepStatusInfo) => { + if (statusInfo.status === 'error' && statusInfo.error) { + setJobError(statusInfo.error); + } + }, []); + return ( - - - In total, this will affect: - - - {hasSubmitted && job ? ( - + <> + + + ) : ( <> + + + + In total, this will affect: + + + {/* Target folder selection */} ({ defaultValues }); - const { editPanel: panelEditor } = dashboard.useState(); - const { handleSubmit, watch } = methods; - const navigate = useNavigate(); - - const [ref, workflow] = watch(['ref', 'workflow']); - const { createBulkJob, isLoading } = useBulkActionJob(); - const [deleteRepoFile, request] = useDeleteRepositoryFilesWithPathMutation(); + // State const [job, setJob] = useState(); const [hasSubmitted, setHasSubmitted] = useState(false); + const [jobError, setJobError] = useState(); + + // Hooks + const navigate = useNavigate(); + const { editPanel: panelEditor } = dashboard.useState(); + const { createBulkJob, isLoading } = useBulkActionJob(); + const [deleteRepoFile, request] = useDeleteRepositoryFilesWithPathMutation(); + // Form + const methods = useForm({ defaultValues }); + const { handleSubmit, watch } = methods; + const [ref, workflow] = watch(['ref', 'workflow']); // Helper function to show error messages const showError = (error?: unknown) => { @@ -136,12 +142,19 @@ export function DeleteProvisionedDashboardForm({ navigate(url); }; - const handleJobStatusChange = (statusInfo: StepStatusInfo) => { - if (statusInfo.status === 'success') { - panelEditor?.onDiscard(); - navigate('/dashboards'); - } - }; + const handleJobStatusChange = useCallback( + (statusInfo: StepStatusInfo) => { + if (statusInfo.status === 'success') { + panelEditor?.onDiscard(); + navigate('/dashboards'); + } + + if (statusInfo.status === 'error' && statusInfo.error) { + setJobError(statusInfo.error); + } + }, + [panelEditor, navigate] + ); useProvisionedRequestHandler({ request, @@ -165,7 +178,10 @@ export function DeleteProvisionedDashboardForm({ onClose={onDismiss} > {hasSubmitted && job ? ( - + <> + + + ) : ( diff --git a/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx index d38b057e632..3968484ceb2 100644 --- a/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx @@ -1,5 +1,5 @@ import { skipToken } from '@reduxjs/toolkit/query'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom-v5-compat'; @@ -19,7 +19,9 @@ import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScen import { JobStatus } from 'app/features/provisioning/Job/JobStatus'; import { StepStatusInfo } from 'app/features/provisioning/Wizard/types'; +import { ProvisioningAlert } from '../../Shared/ProvisioningAlert'; import { ProvisionedOperationInfo, useProvisionedRequestHandler } from '../../hooks/useProvisionedRequestHandler'; +import { StatusInfo } from '../../types'; import { ProvisionedDashboardFormData } from '../../types/form'; import { buildResourceBranchRedirectUrl } from '../../utils/redirect'; import { useBulkActionJob } from '../BulkActions/useBulkActionJob'; @@ -73,6 +75,7 @@ export function MoveProvisionedDashboardForm({ const [targetPath, setTargetPath] = useState(''); const [job, setJob] = useState(); const [hasSubmitted, setHasSubmitted] = useState(false); + const [jobError, setJobError] = useState(); const navigate = useNavigate(); @@ -197,13 +200,20 @@ export function MoveProvisionedDashboardForm({ navigate(url); }; - const handleJobStatusChange = (statusInfo: StepStatusInfo) => { - if (statusInfo.status === 'success') { - dashboard.setState({ isDirty: false }); - panelEditor?.onDiscard(); - navigate('/dashboards'); - } - }; + const handleJobStatusChange = useCallback( + (statusInfo: StepStatusInfo) => { + if (statusInfo.status === 'success') { + dashboard.setState({ isDirty: false }); + panelEditor?.onDiscard(); + navigate('/dashboards'); + } + + if (statusInfo.status === 'error' && statusInfo.error) { + setJobError(statusInfo.error); + } + }, + [dashboard, panelEditor, navigate] + ); useProvisionedRequestHandler({ request: moveRequest, @@ -229,7 +239,10 @@ export function MoveProvisionedDashboardForm({ onClose={onDismiss} > {hasSubmitted && job ? ( - + <> + + + ) : (