From 6483bd146dd16efefd3d7cb6e7e3f85d4779233d Mon Sep 17 00:00:00 2001 From: Bikram Choudhury Date: Thu, 23 Oct 2025 18:37:23 +0530 Subject: [PATCH] added copy job list refresh and reset functionality --- .../ContainerCopy/Actions/CopyJobActions.tsx | 27 ++++++++++++++----- .../ContainerCopy/CommandBar/Utils.ts | 4 ++- .../ContainerCopy/ContainerCopyMessages.ts | 3 ++- .../Utils/useCopyJobNavigation.ts | 15 ++++++----- .../MonitorCopyJobRefState.tsx | 12 +++++++++ .../MonitorCopyJobs/MonitorCopyJobs.tsx | 23 ++++++++++++---- src/Explorer/ContainerCopy/index.tsx | 18 ++++++++----- src/Utils/arm/request.ts | 5 ++++ 8 files changed, 80 insertions(+), 27 deletions(-) create mode 100644 src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx diff --git a/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx b/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx index 877cd7e82..a0cbdf7bd 100644 --- a/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx +++ b/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx @@ -15,6 +15,7 @@ import { } from "../CopyJobUtils"; import CreateCopyJobScreensProvider from "../CreateCopyJob/Screens/CreateCopyJobScreensProvider"; import { CopyJobStatusType } from "../Enums"; +import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefState"; import { CopyJobContextState, CopyJobError, CopyJobType, DataTransferJobType } from "../Types"; export const openCreateCopyJobPanel = () => { @@ -27,7 +28,14 @@ export const openCreateCopyJobPanel = () => { ); } +let copyJobsAbortController: AbortController | null = null; + export const getCopyJobs = async (): Promise => { + // Abort previous request if still in-flight + if (copyJobsAbortController) { + copyJobsAbortController.abort(); + } + copyJobsAbortController = new AbortController(); try { const path = buildDataTransferJobPath({ subscriptionId: userContext.subscriptionId, @@ -36,13 +44,18 @@ export const getCopyJobs = async (): Promise => { }); const response: { value: DataTransferJobType[] } = await armRequest({ - host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion: COPY_JOB_API_VERSION + host: configContext.ARM_ENDPOINT, + path, + method: "GET", + apiVersion: COPY_JOB_API_VERSION, + signal: copyJobsAbortController.signal }); const jobs = response.value || []; if (!Array.isArray(jobs)) { throw new Error("Invalid migration job status response: Expected an array of jobs."); } + copyJobsAbortController = null; /* added a lower bound to "0" and upper bound to "100" */ const calculateCompletionPercentage = (processed: number, total: number): number => { @@ -91,7 +104,7 @@ export const getCopyJobs = async (): Promise => { } } -export const submitCreateCopyJob = async (state: CopyJobContextState) => { +export const submitCreateCopyJob = async (state: CopyJobContextState, onSuccess: () => void) => { try { const { source, target, migrationType, jobName } = state; const path = buildDataTransferJobPath({ @@ -117,10 +130,12 @@ export const submitCreateCopyJob = async (state: CopyJobContextState) => { } }; - const response: { value: DataTransferJobType } = await armRequest({ + const response: DataTransferJobType = await armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", body, apiVersion: COPY_JOB_API_VERSION }); - return response.value; + MonitorCopyJobsRefState.getState().ref?.refreshJobList(); + onSuccess(); + return response; } catch (error) { console.error("Error submitting create copy job:", error); throw error; @@ -137,10 +152,10 @@ export const updateCopyJobStatus = async (job: CopyJobType, action: string): Pro action: action }); - const response: { value: DataTransferJobType } = await armRequest({ + const response: DataTransferJobType = await armRequest({ host: configContext.ARM_ENDPOINT, path, method: "POST", apiVersion: COPY_JOB_API_VERSION }); - return response.value; + return response; } catch (error) { const errorMessage = JSON.stringify((error as CopyJobError).message || error.content || error); diff --git a/src/Explorer/ContainerCopy/CommandBar/Utils.ts b/src/Explorer/ContainerCopy/CommandBar/Utils.ts index faaec7779..ec033b8cc 100644 --- a/src/Explorer/ContainerCopy/CommandBar/Utils.ts +++ b/src/Explorer/ContainerCopy/CommandBar/Utils.ts @@ -6,9 +6,11 @@ import { CommandButtonComponentProps } from "../../Controls/CommandButton/Comman import Explorer from "../../Explorer"; import * as Actions from "../Actions/CopyJobActions"; import ContainerCopyMessages from "../ContainerCopyMessages"; +import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefState"; import { CopyJobCommandBarBtnType } from "../Types"; function getCopyJobBtns(): CopyJobCommandBarBtnType[] { + const monitorCopyJobsRef = MonitorCopyJobsRefState(state => state.ref); const buttons: CopyJobCommandBarBtnType[] = [ { key: "createCopyJob", @@ -22,7 +24,7 @@ function getCopyJobBtns(): CopyJobCommandBarBtnType[] { iconSrc: RefreshIcon, label: ContainerCopyMessages.refreshButtonLabel, ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel, - onClick: () => { }, + onClick: () => monitorCopyJobsRef?.refreshJobList(), }, ]; if (configContext.platform === Platform.Portal) { diff --git a/src/Explorer/ContainerCopy/ContainerCopyMessages.ts b/src/Explorer/ContainerCopy/ContainerCopyMessages.ts index 2e66c3c46..35890a88b 100644 --- a/src/Explorer/ContainerCopy/ContainerCopyMessages.ts +++ b/src/Explorer/ContainerCopy/ContainerCopyMessages.ts @@ -110,7 +110,8 @@ export default { Completed: "Completed", Failed: "Failed", Faulted: "Failed", - Skipped: "Canceled", + Skipped: "Cancelled", + Cancelled: "Cancelled", } } } \ No newline at end of file diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts index 86a43f174..9332c3e51 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts @@ -34,7 +34,7 @@ function navigationReducer(state: NavigationState, action: Action): NavigationSt } export function useCopyJobNavigation() { - const { copyJobState } = useCopyJobContext(); + const { copyJobState, resetCopyJobState } = useCopyJobContext(); const screens = useCreateCopyJobScreensList(); const { validationCache: cache } = useCopyJobPrerequisitesCache(); const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] }); @@ -58,6 +58,12 @@ export function useCopyJobNavigation() { const isPreviousDisabled = state.screenHistory.length <= 1; + const handleCancel = useCallback(() => { + dispatch({ type: "RESET" }); + resetCopyJobState(); + useSidePanel.getState().closeSidePanel(); + }, []); + const handlePrimary = useCallback(() => { const transitions = { [SCREEN_KEYS.SelectAccount]: SCREEN_KEYS.AssignPermissions, @@ -69,7 +75,7 @@ export function useCopyJobNavigation() { if (nextScreen) { dispatch({ type: "NEXT", nextScreen }); } else if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) { - submitCreateCopyJob(copyJobState); + submitCreateCopyJob(copyJobState, handleCancel); } }, [currentScreenKey, copyJobState]); @@ -77,11 +83,6 @@ export function useCopyJobNavigation() { dispatch({ type: "PREVIOUS" }); }, []); - const handleCancel = useCallback(() => { - dispatch({ type: "RESET" }); - useSidePanel.getState().closeSidePanel(); - }, []); - return { currentScreen, isPrimaryDisabled, diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx new file mode 100644 index 000000000..1227dfd1a --- /dev/null +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx @@ -0,0 +1,12 @@ +import create from "zustand"; +import { MonitorCopyJobsRef } from "./MonitorCopyJobs"; + +type MonitorCopyJobsRefStateType = { + ref: MonitorCopyJobsRef; + setRef: (ref: MonitorCopyJobsRef) => void; +}; + +export const MonitorCopyJobsRefState = create((set) => ({ + ref: null, + setRef: (ref) => set({ ref: ref }), +})); \ No newline at end of file diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx index baffd0c1d..633030325 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx @@ -1,6 +1,6 @@ import { MessageBar, MessageBarType, Stack } from '@fluentui/react'; import ShimmerTree, { IndentLevel } from 'Common/ShimmerTree'; -import React, { useEffect } from 'react'; +import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; import { getCopyJobs, updateCopyJobStatus } from '../Actions/CopyJobActions'; import { convertToCamelCase } from '../CopyJobUtils'; import { CopyJobStatusType } from '../Enums'; @@ -12,7 +12,11 @@ const FETCH_INTERVAL_MS = 30 * 1000; // Interval time in milliseconds (30 second interface MonitorCopyJobsProps { } -const MonitorCopyJobs: React.FC = () => { +export interface MonitorCopyJobsRef { + refreshJobList: () => void; +} + +const MonitorCopyJobs = forwardRef((_props, ref) => { const [loading, setLoading] = React.useState(true); // Start with loading as true const [error, setError] = React.useState(null); const [jobs, setJobs] = React.useState([]); @@ -48,12 +52,21 @@ const MonitorCopyJobs: React.FC = () => { useEffect(() => { fetchJobs(); - const intervalId = setInterval(fetchJobs, FETCH_INTERVAL_MS); return () => clearInterval(intervalId); }, [fetchJobs]); + useImperativeHandle(ref, () => ({ + refreshJobList: () => { + if (isUpdatingRef.current) { + setError("Please wait for the current update to complete before refreshing."); + return; + } + fetchJobs(); + } + })); + const handleActionClick = React.useCallback(async (job: CopyJobType, action: string) => { try { isUpdatingRef.current = true; // Mark as updating @@ -64,7 +77,7 @@ const MonitorCopyJobs: React.FC = () => { prevJob.Name === updatedCopyJob.properties.jobName ? { ...prevJob, - MigrationStatus: convertToCamelCase(updatedCopyJob.properties.status) as CopyJobStatusType + Status: convertToCamelCase(updatedCopyJob.properties.status) as CopyJobStatusType } : prevJob ) ); @@ -97,6 +110,6 @@ const MonitorCopyJobs: React.FC = () => { {memoizedJobsList} ); -} +}); export default MonitorCopyJobs; \ No newline at end of file diff --git a/src/Explorer/ContainerCopy/index.tsx b/src/Explorer/ContainerCopy/index.tsx index ca0bbd2fd..e54bf1821 100644 --- a/src/Explorer/ContainerCopy/index.tsx +++ b/src/Explorer/ContainerCopy/index.tsx @@ -1,17 +1,21 @@ -import React, { Suspense } from 'react'; +import { MonitorCopyJobsRefState } from 'Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState'; +import React, { useEffect } from 'react'; import CopyJobCommandBar from './CommandBar/CopyJobCommandBar'; -import { ContainerCopyProps } from './Types'; import './containerCopyStyles.less'; - -const MonitorCopyJobs = React.lazy(() => import('./MonitorCopyJobs/MonitorCopyJobs')); +import MonitorCopyJobs, { MonitorCopyJobsRef } from './MonitorCopyJobs/MonitorCopyJobs'; +import { ContainerCopyProps } from './Types'; const ContainerCopyPanel: React.FC = ({ container }) => { + const monitorCopyJobsRef = React.useRef(); + useEffect(() => { + if (monitorCopyJobsRef.current) { + MonitorCopyJobsRefState.getState().setRef(monitorCopyJobsRef.current); + } + }, [monitorCopyJobsRef.current]); return (
- Loading...
}> - - + ); }; diff --git a/src/Utils/arm/request.ts b/src/Utils/arm/request.ts index 5e3ebb004..c9627ca1a 100644 --- a/src/Utils/arm/request.ts +++ b/src/Utils/arm/request.ts @@ -48,6 +48,7 @@ interface Options { queryParams?: ARMQueryParams; contentType?: string; customHeaders?: Record; + signal?: AbortSignal; } export async function armRequestWithoutPolling({ @@ -59,6 +60,7 @@ export async function armRequestWithoutPolling({ queryParams, contentType, customHeaders, + signal, }: Options): Promise<{ result: T; operationStatusUrl: string }> { const url = new URL(path, host); url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion); @@ -81,6 +83,7 @@ export async function armRequestWithoutPolling({ method, headers, body: requestBody ? JSON.stringify(requestBody) : undefined, + signal }); if (!response.ok) { @@ -116,6 +119,7 @@ export async function armRequest({ queryParams, contentType, customHeaders, + signal }: Options): Promise { const armRequestResult = await armRequestWithoutPolling({ host, @@ -126,6 +130,7 @@ export async function armRequest({ queryParams, contentType, customHeaders, + signal }); const operationStatusUrl = armRequestResult.operationStatusUrl; if (operationStatusUrl) {