added copy job list refresh and reset functionality

This commit is contained in:
Bikram Choudhury
2025-10-23 18:37:23 +05:30
parent 7b437b62ce
commit 6483bd146d
8 changed files with 80 additions and 27 deletions

View File

@@ -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<CopyJobType[]> => {
// 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<CopyJobType[]> => {
});
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<CopyJobType[]> => {
}
}
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);

View File

@@ -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) {

View File

@@ -110,7 +110,8 @@ export default {
Completed: "Completed",
Failed: "Failed",
Faulted: "Failed",
Skipped: "Canceled",
Skipped: "Cancelled",
Cancelled: "Cancelled",
}
}
}

View File

@@ -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,

View File

@@ -0,0 +1,12 @@
import create from "zustand";
import { MonitorCopyJobsRef } from "./MonitorCopyJobs";
type MonitorCopyJobsRefStateType = {
ref: MonitorCopyJobsRef;
setRef: (ref: MonitorCopyJobsRef) => void;
};
export const MonitorCopyJobsRefState = create<MonitorCopyJobsRefStateType>((set) => ({
ref: null,
setRef: (ref) => set({ ref: ref }),
}));

View File

@@ -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<MonitorCopyJobsProps> = () => {
export interface MonitorCopyJobsRef {
refreshJobList: () => void;
}
const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_props, ref) => {
const [loading, setLoading] = React.useState(true); // Start with loading as true
const [error, setError] = React.useState<string | null>(null);
const [jobs, setJobs] = React.useState<CopyJobType[]>([]);
@@ -48,12 +52,21 @@ const MonitorCopyJobs: React.FC<MonitorCopyJobsProps> = () => {
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<MonitorCopyJobsProps> = () => {
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<MonitorCopyJobsProps> = () => {
{memoizedJobsList}
</Stack>
);
}
});
export default MonitorCopyJobs;

View File

@@ -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<ContainerCopyProps> = ({ container }) => {
const monitorCopyJobsRef = React.useRef<MonitorCopyJobsRef>();
useEffect(() => {
if (monitorCopyJobsRef.current) {
MonitorCopyJobsRefState.getState().setRef(monitorCopyJobsRef.current);
}
}, [monitorCopyJobsRef.current]);
return (
<div id="containerCopyWrapper" className="flexContainer hideOverflows">
<CopyJobCommandBar container={container} />
<Suspense fallback={<div>Loading...</div>}>
<MonitorCopyJobs />
</Suspense>
<MonitorCopyJobs ref={monitorCopyJobsRef} />
</div>
);
};

View File

@@ -48,6 +48,7 @@ interface Options {
queryParams?: ARMQueryParams;
contentType?: string;
customHeaders?: Record<string, string>;
signal?: AbortSignal;
}
export async function armRequestWithoutPolling<T>({
@@ -59,6 +60,7 @@ export async function armRequestWithoutPolling<T>({
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<T>({
method,
headers,
body: requestBody ? JSON.stringify(requestBody) : undefined,
signal
});
if (!response.ok) {
@@ -116,6 +119,7 @@ export async function armRequest<T>({
queryParams,
contentType,
customHeaders,
signal
}: Options): Promise<T> {
const armRequestResult = await armRequestWithoutPolling<T>({
host,
@@ -126,6 +130,7 @@ export async function armRequest<T>({
queryParams,
contentType,
customHeaders,
signal
});
const operationStatusUrl = armRequestResult.operationStatusUrl;
if (operationStatusUrl) {