Container Copy Job implementation for SQL accounts (#2241)

* Initial dev for container copy

* remove padding from label

* Added Copy Job prerequisites screen

* Added hooks to evaluate reader role access

* added copyjob pre-requsite screen along with it's validations

* Added monitor copy job list screen

* added copy job list refresh and reset functionality

* remove arm token dependency

* fetch account details from account id instead of context

* Fix lint & typescript checks

* show copyjob screen from portal navigation

* adding copy job details screen

* remove duplicate code & show sql accounts only

* ui fixes for list job page

* pending icon

* copy job details screen ui

* reset .vscode/settings.json

* Fixed existing UTs

* disabling action buttons until it's in progress

* fixed formatting

* Adding loader on submit button and show job creation errors in the panel itself

* updating disabling action menu item logic

* added custom pager

* fix lint and ts errors

* updating file names and removing comments

* remove comments

* modularize the arom common code

* Adding content and removing tooltip

* updating job details screen

* updating online copy enabled screen

* Adding below changes
- Don't show permission screen for same account in offline mode
- Don't show identity permissions for same account in online mode
- Show error message if selected containers are identical
- Update abort signal messages

* added feedback code from explorer

* Add tooltips and long polling
- Added tooltips to permission sections
- Implemented long polling for PITR and online copy enabled sections
- Long polling automatically stops after 15 minutes
- After polling ends, a refresh button will be displayed

---------

Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
This commit is contained in:
BChoudhury-ms
2025-11-05 22:54:00 +05:30
committed by GitHub
parent 3718f5a16a
commit 2417da152d
78 changed files with 4152 additions and 36 deletions

View File

@@ -0,0 +1,152 @@
import { useCallback, useMemo, useReducer, useState } from "react";
import { useSidePanel } from "../../../../hooks/useSidePanel";
import { submitCreateCopyJob } from "../../Actions/CopyJobActions";
import { useCopyJobContext } from "../../Context/CopyJobContext";
import { CopyJobMigrationType } from "../../Enums/CopyJobEnums";
import { useCopyJobPrerequisitesCache } from "./useCopyJobPrerequisitesCache";
import { SCREEN_KEYS, useCreateCopyJobScreensList } from "./useCreateCopyJobScreensList";
type NavigationState = {
screenHistory: string[];
};
type Action = { type: "NEXT"; nextScreen: string } | { type: "PREVIOUS" } | { type: "RESET" };
function navigationReducer(state: NavigationState, action: Action): NavigationState {
switch (action.type) {
case "NEXT":
return {
screenHistory: [...state.screenHistory, action.nextScreen],
};
case "PREVIOUS":
return {
screenHistory: state.screenHistory.length > 1 ? state.screenHistory.slice(0, -1) : state.screenHistory,
};
case "RESET":
return {
screenHistory: [SCREEN_KEYS.SelectAccount],
};
default:
return state;
}
}
export function useCopyJobNavigation() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { copyJobState, resetCopyJobState } = useCopyJobContext();
const screens = useCreateCopyJobScreensList();
const { validationCache: cache } = useCopyJobPrerequisitesCache();
const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] });
const currentScreenKey = state.screenHistory[state.screenHistory.length - 1];
const currentScreen = screens.find((screen) => screen.key === currentScreenKey);
const isPrimaryDisabled = useMemo(() => {
if (isLoading) {
return true;
}
const context = currentScreenKey === SCREEN_KEYS.AssignPermissions ? cache : copyJobState;
return !currentScreen?.validations.every((v) => v.validate(context));
}, [currentScreen.key, copyJobState, cache, isLoading]);
const primaryBtnText = useMemo(() => {
if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) {
return "Copy";
}
return "Next";
}, [currentScreenKey]);
const isPreviousDisabled = state.screenHistory.length <= 1;
const handleCancel = useCallback(() => {
dispatch({ type: "RESET" });
resetCopyJobState();
useSidePanel.getState().closeSidePanel();
}, []);
const getContainerIdentifiers = (container: typeof copyJobState.source | typeof copyJobState.target) => ({
accountId: container?.account?.id || "",
databaseId: container?.databaseId || "",
containerId: container?.containerId || "",
});
const isSameAccount = (
sourceIds: ReturnType<typeof getContainerIdentifiers>,
targetIds: ReturnType<typeof getContainerIdentifiers>,
) => sourceIds.accountId === targetIds.accountId;
const areContainersIdentical = () => {
const { source, target } = copyJobState;
const sourceIds = getContainerIdentifiers(source);
const targetIds = getContainerIdentifiers(target);
return (
isSameAccount(sourceIds, targetIds) &&
sourceIds.databaseId === targetIds.databaseId &&
sourceIds.containerId === targetIds.containerId
);
};
const shouldNotShowPermissionScreen = () => {
const { source, target, migrationType } = copyJobState;
return (
migrationType === CopyJobMigrationType.Offline &&
isSameAccount(getContainerIdentifiers(source), getContainerIdentifiers(target))
);
};
const handleCopyJobSubmission = async () => {
try {
setIsLoading(true);
await submitCreateCopyJob(copyJobState, handleCancel);
} catch (error: unknown) {
const errorMessage =
error instanceof Error
? error.message || "Failed to create copy job. Please try again later."
: "Failed to create copy job. Please try again later.";
setError(errorMessage);
} finally {
setIsLoading(false);
}
};
const handlePrimary = useCallback(() => {
if (currentScreenKey === SCREEN_KEYS.SelectSourceAndTargetContainers && areContainersIdentical()) {
setError("Source and destination containers cannot be the same. Please select different containers to proceed.");
return;
}
setError(null);
const transitions = {
[SCREEN_KEYS.SelectAccount]: shouldNotShowPermissionScreen()
? SCREEN_KEYS.SelectSourceAndTargetContainers
: SCREEN_KEYS.AssignPermissions,
[SCREEN_KEYS.AssignPermissions]: SCREEN_KEYS.SelectSourceAndTargetContainers,
[SCREEN_KEYS.SelectSourceAndTargetContainers]: SCREEN_KEYS.PreviewCopyJob,
};
const nextScreen = transitions[currentScreenKey];
if (nextScreen) {
dispatch({ type: "NEXT", nextScreen });
} else if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) {
handleCopyJobSubmission();
}
}, [currentScreenKey, copyJobState, areContainersIdentical, handleCopyJobSubmission]);
const handlePrevious = useCallback(() => {
dispatch({ type: "PREVIOUS" });
}, []);
return {
currentScreen,
isPrimaryDisabled,
isPreviousDisabled,
handlePrimary,
handlePrevious,
handleCancel,
primaryBtnText,
error,
setError,
};
}