mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-28 13:21:42 +00:00
added copy job list refresh and reset functionality
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -110,7 +110,8 @@ export default {
|
||||
Completed: "Completed",
|
||||
Failed: "Failed",
|
||||
Faulted: "Failed",
|
||||
Skipped: "Canceled",
|
||||
Skipped: "Cancelled",
|
||||
Cancelled: "Cancelled",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
}));
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user