From 58e187aeb200b53f7695418578e0bf0944f69559 Mon Sep 17 00:00:00 2001 From: Bikram Choudhury Date: Tue, 28 Oct 2025 18:05:05 +0530 Subject: [PATCH] Fix lint & typescript checks --- src/Common/DatabaseAccountUtility.ts | 2 +- src/Common/ShimmerTree/index.tsx | 58 ++-- src/Contracts/DataModels.ts | 4 +- .../ContainerCopy/Actions/CopyJobActions.tsx | 306 ++++++++--------- .../CommandBar/CopyJobCommandBar.tsx | 32 +- .../ContainerCopy/CommandBar/Utils.ts | 78 ++--- .../ContainerCopy/ContainerCopyMessages.ts | 229 +++++++------ .../ContainerCopy/Context/CopyJobContext.tsx | 74 ++--- src/Explorer/ContainerCopy/CopyJobUtils.ts | 133 ++++---- .../AssignPermissions/AddManagedIdentity.tsx | 83 ++--- .../AddReadPermissionToDefaultIdentity.tsx | 120 +++---- .../DefaultManagedIdentity.tsx | 59 ++-- .../AssignPermissions/OnlineCopyEnabled.tsx | 32 +- .../AssignPermissions/PointInTimeRestore.tsx | 82 +++-- .../hooks/useManagedIdentity.tsx | 72 ++-- .../hooks/usePermissionsSection.tsx | 308 +++++++++--------- .../AssignPermissions/hooks/useToggle.tsx | 12 +- .../hooks/useWindowOpenMonitor.tsx | 47 ++- .../Screens/AssignPermissions/index.tsx | 115 +++---- .../Screens/Components/FieldRow.tsx | 32 +- .../Screens/Components/InfoTooltip.tsx | 18 +- .../Screens/Components/NavigationControls.tsx | 36 +- .../Screens/Components/PopoverContainer.tsx | 83 +++-- .../Screens/CreateCopyJobScreens.tsx | 48 +-- .../Screens/CreateCopyJobScreensProvider.tsx | 12 +- .../Utils/PreviewCopyJobUtils.ts | 72 ++-- .../Screens/PreviewCopyJob/index.tsx | 91 +++--- .../Components/AccountDropdown.tsx | 38 ++- .../Components/MigrationTypeCheckbox.tsx | 22 +- .../Components/SubscriptionDropdown.tsx | 34 +- .../Utils/selectAccountUtils.tsx | 120 +++---- .../Screens/SelectAccount/index.tsx | 64 ++-- .../Events/DropDownChangeHandler.tsx | 63 ++-- .../components/DatabaseContainerSection.tsx | 68 ++-- .../SelectSourceAndTargetContainers/index.tsx | 120 ++++--- .../memoizedData.tsx | 86 ++--- .../Utils/useCopyJobNavigation.ts | 140 ++++---- .../Utils/useCopyJobPrerequisitesCache.tsx | 10 +- .../Utils/useCreateCopyJobScreensList.tsx | 132 ++++---- src/Explorer/ContainerCopy/Enums/index.ts | 48 +-- .../Components/CopyJobActionMenu.tsx | 121 ++++--- .../Components/CopyJobColumns.tsx | 138 ++++---- .../Components/CopyJobStatusWithIcon.tsx | 67 ++-- .../Components/CopyJobs.NotFound.tsx | 31 +- .../Components/CopyJobsList.tsx | 159 ++++----- .../MonitorCopyJobRefState.tsx | 10 +- .../MonitorCopyJobs/MonitorCopyJobs.tsx | 195 +++++------ src/Explorer/ContainerCopy/Types/index.ts | 184 +++++------ src/Explorer/ContainerCopy/index.tsx | 38 +-- .../Panes/PanelContainerComponent.tsx | 2 +- src/Main.tsx | 18 +- src/Utils/AuthorizationUtils.test.ts | 1 + src/Utils/CopyJobAuthUtils.ts | 15 +- src/Utils/arm/RbacUtils.ts | 153 ++++----- src/Utils/arm/databaseAccountUtils.ts | 53 ++- .../dataTransferService/dataTransferJobs.ts | 2 +- .../dataTransferService/types.ts | 117 ++++--- src/Utils/arm/identityUtils.ts | 79 ++--- src/Utils/arm/request.ts | 6 +- src/hooks/useDataContainers.tsx | 109 +++---- src/hooks/useDatabaseAccounts.tsx | 21 +- src/hooks/useDatabases.tsx | 87 +++-- 62 files changed, 2376 insertions(+), 2413 deletions(-) diff --git a/src/Common/DatabaseAccountUtility.ts b/src/Common/DatabaseAccountUtility.ts index af3e1b699..41f8c4b0a 100644 --- a/src/Common/DatabaseAccountUtility.ts +++ b/src/Common/DatabaseAccountUtility.ts @@ -62,4 +62,4 @@ export const getCollectionEndpoint = (apiType: ApiType): string => { case "SQL": return "containers"; } -}; \ No newline at end of file +}; diff --git a/src/Common/ShimmerTree/index.tsx b/src/Common/ShimmerTree/index.tsx index ef3579233..75f4d3ddf 100644 --- a/src/Common/ShimmerTree/index.tsx +++ b/src/Common/ShimmerTree/index.tsx @@ -2,41 +2,39 @@ import { Shimmer, ShimmerElementType, Stack } from "@fluentui/react"; import * as React from "react"; export interface IndentLevel { - level: number, - width?: string + level: number; + width?: string; } interface ShimmerTreeProps { - indentLevels: IndentLevel[]; - style?: React.CSSProperties; + indentLevels: IndentLevel[]; + style?: React.CSSProperties; } const ShimmerTree = ({ indentLevels, style = {} }: ShimmerTreeProps) => { - /** - * indentLevels - Array of indent levels for shimmer tree - * 0 - Root - * 1 - Level 1 - * 2 - Level 2 - * 3 - Level 3 - * n - Level n - * */ - const renderShimmers = (indent: IndentLevel) => ( - - ); + /** + * indentLevels - Array of indent levels for shimmer tree + * 0 - Root + * 1 - Level 1 + * 2 - Level 2 + * 3 - Level 3 + * n - Level n + * */ + const renderShimmers = (indent: IndentLevel) => ( + + ); - return ( - - { - indentLevels.map((indentLevel: IndentLevel) => renderShimmers(indentLevel)) - } - - ); + return ( + + {indentLevels.map((indentLevel: IndentLevel) => renderShimmers(indentLevel))} + + ); }; -export default ShimmerTree; \ No newline at end of file +export default ShimmerTree; diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index b05d7ebac..a9dc8a828 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -14,7 +14,7 @@ export interface DatabaseAccountUserAssignedIdentity { [key: string]: { principalId: string; clientId: string; - } + }; } export interface DatabaseAccountIdentity { @@ -226,7 +226,7 @@ export interface Database extends Resource { collections?: Collection[]; } -export interface DocumentId extends Resource { } +export interface DocumentId extends Resource {} export interface ConflictId extends Resource { resourceId?: string; diff --git a/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx b/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx index 6e14f91ed..9ba55f821 100644 --- a/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx +++ b/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx @@ -2,22 +2,25 @@ import React from "react"; import { userContext } from "UserContext"; import { useSidePanel } from "../../../hooks/useSidePanel"; import { - cancel, - complete, - create, - listByDatabaseAccount, - pause, - resume + cancel, + complete, + create, + listByDatabaseAccount, + pause, + resume, } from "../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs"; -import { CreateJobRequest, DataTransferJobGetResults } from "../../../Utils/arm/generatedClients/dataTransferService/types"; +import { + CreateJobRequest, + DataTransferJobGetResults, +} from "../../../Utils/arm/generatedClients/dataTransferService/types"; import ContainerCopyMessages from "../ContainerCopyMessages"; import { - convertTime, - convertToCamelCase, - COSMOS_SQL_COMPONENT, - extractErrorMessage, - formatUTCDateTime, - getAccountDetailsFromResourceId + convertTime, + convertToCamelCase, + COSMOS_SQL_COMPONENT, + extractErrorMessage, + formatUTCDateTime, + getAccountDetailsFromResourceId, } from "../CopyJobUtils"; import CreateCopyJobScreensProvider from "../CreateCopyJob/Screens/CreateCopyJobScreensProvider"; import { CopyJobActions, CopyJobStatusType } from "../Enums"; @@ -25,154 +28,159 @@ import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefSta import { CopyJobContextState, CopyJobError, CopyJobErrorType, CopyJobType } from "../Types"; export const openCreateCopyJobPanel = () => { - const sidePanelState = useSidePanel.getState() - sidePanelState.setPanelHasConsole(false); - sidePanelState.openSidePanel( - ContainerCopyMessages.createCopyJobPanelTitle, - , - "650px" - ); -} + const sidePanelState = useSidePanel.getState(); + sidePanelState.setPanelHasConsole(false); + sidePanelState.openSidePanel( + ContainerCopyMessages.createCopyJobPanelTitle, + , + "650px", + ); +}; let copyJobsAbortController: AbortController | null = null; export const getCopyJobs = async (): Promise => { - // Abort previous request if still in-flight - if (copyJobsAbortController) { - copyJobsAbortController.abort(); + // Abort previous request if still in-flight + if (copyJobsAbortController) { + copyJobsAbortController.abort(); + } + copyJobsAbortController = new AbortController(); + try { + const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId( + userContext.databaseAccount?.id || "", + ); + const response = await listByDatabaseAccount( + subscriptionId, + resourceGroup, + accountName, + copyJobsAbortController.signal, + ); + + const jobs = response.value || []; + if (!Array.isArray(jobs)) { + throw new Error("Invalid migration job status response: Expected an array of jobs."); } - copyJobsAbortController = new AbortController(); - try { - const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(userContext.databaseAccount?.id || ""); - const response = await listByDatabaseAccount( - subscriptionId, - resourceGroup, - accountName, - copyJobsAbortController.signal - ); + copyJobsAbortController = null; - 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 => { + if ( + typeof processed !== "number" || + typeof total !== "number" || + !isFinite(processed) || + !isFinite(total) || + total <= 0 + ) { + return 0; + } - /* added a lower bound to "0" and upper bound to "100" */ - const calculateCompletionPercentage = (processed: number, total: number): number => { - if ( - typeof processed !== 'number' || - typeof total !== 'number' || - !isFinite(processed) || - !isFinite(total) || - total <= 0 - ) { - return 0; - } + const percentage = Math.round((processed / total) * 100); + return Math.max(0, Math.min(100, percentage)); + }; - const percentage = Math.round((processed / total) * 100); - return Math.max(0, Math.min(100, percentage)); - }; + const formattedJobs: CopyJobType[] = jobs + .filter( + (job: DataTransferJobGetResults) => + job.properties?.source?.component === COSMOS_SQL_COMPONENT && + job.properties?.destination?.component === COSMOS_SQL_COMPONENT, + ) + .sort( + (current: DataTransferJobGetResults, next: DataTransferJobGetResults) => + new Date(next.properties.lastUpdatedUtcTime).getTime() - + new Date(current.properties.lastUpdatedUtcTime).getTime(), + ) + .map((job: DataTransferJobGetResults, index: number) => { + const dateTimeObj = formatUTCDateTime(job.properties.lastUpdatedUtcTime); - const formattedJobs: CopyJobType[] = jobs - .filter((job: DataTransferJobGetResults) => - job.properties?.source?.component === COSMOS_SQL_COMPONENT && - job.properties?.destination?.component === COSMOS_SQL_COMPONENT - ) - .sort((current: DataTransferJobGetResults, next: DataTransferJobGetResults) => - new Date(next.properties.lastUpdatedUtcTime).getTime() - new Date(current.properties.lastUpdatedUtcTime).getTime() - ) - .map((job: DataTransferJobGetResults, index: number) => { - const dateTimeObj = formatUTCDateTime(job.properties.lastUpdatedUtcTime); - - return { - ID: (index + 1).toString(), - Mode: job.properties.mode, - Name: job.properties.jobName, - Status: convertToCamelCase(job.properties.status) as CopyJobType["Status"], - CompletionPercentage: calculateCompletionPercentage(job.properties.processedCount, job.properties.totalCount), - Duration: convertTime(job.properties.duration), - LastUpdatedTime: dateTimeObj.formattedDateTime, - timestamp: dateTimeObj.timestamp, - Error: job.properties.error ? extractErrorMessage(job.properties.error as unknown as CopyJobErrorType) : null, - } as CopyJobType; - }); - return formattedJobs; - } catch (error) { - const errorContent = JSON.stringify(error.content || error); - console.error(`Error fetching copy jobs: ${errorContent}`); - throw error; - } -} + return { + ID: (index + 1).toString(), + Mode: job.properties.mode, + Name: job.properties.jobName, + Status: convertToCamelCase(job.properties.status) as CopyJobType["Status"], + CompletionPercentage: calculateCompletionPercentage(job.properties.processedCount, job.properties.totalCount), + Duration: convertTime(job.properties.duration), + LastUpdatedTime: dateTimeObj.formattedDateTime, + timestamp: dateTimeObj.timestamp, + Error: job.properties.error ? extractErrorMessage(job.properties.error as unknown as CopyJobErrorType) : null, + } as CopyJobType; + }); + return formattedJobs; + } catch (error) { + const errorContent = JSON.stringify(error.content || error); + console.error(`Error fetching copy jobs: ${errorContent}`); + throw error; + } +}; export const submitCreateCopyJob = async (state: CopyJobContextState, onSuccess: () => void) => { - try { - const { source, target, migrationType, jobName } = state; - const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(userContext.databaseAccount?.id || ""); - const body = { - properties: { - source: { - component: "CosmosDBSql", - remoteAccountName: source?.account?.name, - databaseName: source?.databaseId, - containerName: source?.containerId - }, - destination: { - component: "CosmosDBSql", - databaseName: target?.databaseId, - containerName: target?.containerId - }, - mode: migrationType - } - } as unknown as CreateJobRequest; + try { + const { source, target, migrationType, jobName } = state; + const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId( + userContext.databaseAccount?.id || "", + ); + const body = { + properties: { + source: { + component: "CosmosDBSql", + remoteAccountName: source?.account?.name, + databaseName: source?.databaseId, + containerName: source?.containerId, + }, + destination: { + component: "CosmosDBSql", + databaseName: target?.databaseId, + containerName: target?.containerId, + }, + mode: migrationType, + }, + } as unknown as CreateJobRequest; - const response = await create( - subscriptionId, - resourceGroup, - accountName, - jobName, - body, - ); - MonitorCopyJobsRefState.getState().ref?.refreshJobList(); - onSuccess(); - return response; - } catch (error) { - console.error("Error submitting create copy job:", error); - throw error; - } -} + const response = await create(subscriptionId, resourceGroup, accountName, jobName, body); + MonitorCopyJobsRefState.getState().ref?.refreshJobList(); + onSuccess(); + return response; + } catch (error) { + console.error("Error submitting create copy job:", error); + throw error; + } +}; export const updateCopyJobStatus = async (job: CopyJobType, action: string): Promise => { - try { - - let updateFn = null; - switch (action.toLowerCase()) { - case CopyJobActions.pause: - updateFn = pause; - break; - case CopyJobActions.resume: - updateFn = resume; - break; - case CopyJobActions.cancel: - updateFn = cancel; - break; - case CopyJobActions.complete: - updateFn = complete; - break; - default: - throw new Error(`Unsupported action: ${action}`); - } - const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(userContext.databaseAccount?.id || ""); - const response = await updateFn?.(subscriptionId, resourceGroup, accountName, job.Name); - - return response; - } catch (error) { - const errorMessage = JSON.stringify((error as CopyJobError).message || error.content || error); - - const statusList = [CopyJobStatusType.Running, CopyJobStatusType.InProgress, CopyJobStatusType.Partitioning]; - const pattern = new RegExp(`'(${statusList.join('|')})'`, 'g'); - const normalizedErrorMessage = errorMessage.replace(pattern, `'${ContainerCopyMessages.MonitorJobs.Status.InProgress}'`); - - console.error(`Error updating copy job status: ${normalizedErrorMessage}`); - throw error; + try { + let updateFn = null; + switch (action.toLowerCase()) { + case CopyJobActions.pause: + updateFn = pause; + break; + case CopyJobActions.resume: + updateFn = resume; + break; + case CopyJobActions.cancel: + updateFn = cancel; + break; + case CopyJobActions.complete: + updateFn = complete; + break; + default: + throw new Error(`Unsupported action: ${action}`); } -} + const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId( + userContext.databaseAccount?.id || "", + ); + const response = await updateFn?.(subscriptionId, resourceGroup, accountName, job.Name); + + return response; + } catch (error) { + const errorMessage = JSON.stringify((error as CopyJobError).message || error.content || error); + + const statusList = [CopyJobStatusType.Running, CopyJobStatusType.InProgress, CopyJobStatusType.Partitioning]; + const pattern = new RegExp(`'(${statusList.join("|")})'`, "g"); + const normalizedErrorMessage = errorMessage.replace( + pattern, + `'${ContainerCopyMessages.MonitorJobs.Status.InProgress}'`, + ); + + console.error(`Error updating copy job status: ${normalizedErrorMessage}`); + throw error; + } +}; diff --git a/src/Explorer/ContainerCopy/CommandBar/CopyJobCommandBar.tsx b/src/Explorer/ContainerCopy/CommandBar/CopyJobCommandBar.tsx index 724cecd15..da9ddc9ae 100644 --- a/src/Explorer/ContainerCopy/CommandBar/CopyJobCommandBar.tsx +++ b/src/Explorer/ContainerCopy/CommandBar/CopyJobCommandBar.tsx @@ -8,24 +8,24 @@ import { getCommandBarButtons } from "./Utils"; const backgroundColor = StyleConstants.BaseLight; const rootStyle = { - root: { - backgroundColor: backgroundColor, - }, + root: { + backgroundColor: backgroundColor, + }, }; const CopyJobCommandBar: React.FC = ({ container }) => { - const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(container); - const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor); + const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(container); + const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor); - return ( -
- -
- ); -} + return ( +
+ +
+ ); +}; -export default CopyJobCommandBar; \ No newline at end of file +export default CopyJobCommandBar; diff --git a/src/Explorer/ContainerCopy/CommandBar/Utils.ts b/src/Explorer/ContainerCopy/CommandBar/Utils.ts index ec033b8cc..325573823 100644 --- a/src/Explorer/ContainerCopy/CommandBar/Utils.ts +++ b/src/Explorer/ContainerCopy/CommandBar/Utils.ts @@ -10,49 +10,49 @@ import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefSta import { CopyJobCommandBarBtnType } from "../Types"; function getCopyJobBtns(): CopyJobCommandBarBtnType[] { - const monitorCopyJobsRef = MonitorCopyJobsRefState(state => state.ref); - const buttons: CopyJobCommandBarBtnType[] = [ - { - key: "createCopyJob", - iconSrc: AddIcon, - label: ContainerCopyMessages.createCopyJobButtonLabel, - ariaLabel: ContainerCopyMessages.createCopyJobButtonAriaLabel, - onClick: Actions.openCreateCopyJobPanel, - }, - { - key: "refresh", - iconSrc: RefreshIcon, - label: ContainerCopyMessages.refreshButtonLabel, - ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel, - onClick: () => monitorCopyJobsRef?.refreshJobList(), - }, - ]; - if (configContext.platform === Platform.Portal) { - buttons.push({ - key: "feedback", - iconSrc: FeedbackIcon, - label: ContainerCopyMessages.feedbackButtonLabel, - ariaLabel: ContainerCopyMessages.feedbackButtonAriaLabel, - onClick: () => { }, - }); - } - return buttons; + const monitorCopyJobsRef = MonitorCopyJobsRefState((state) => state.ref); + const buttons: CopyJobCommandBarBtnType[] = [ + { + key: "createCopyJob", + iconSrc: AddIcon, + label: ContainerCopyMessages.createCopyJobButtonLabel, + ariaLabel: ContainerCopyMessages.createCopyJobButtonAriaLabel, + onClick: Actions.openCreateCopyJobPanel, + }, + { + key: "refresh", + iconSrc: RefreshIcon, + label: ContainerCopyMessages.refreshButtonLabel, + ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel, + onClick: () => monitorCopyJobsRef?.refreshJobList(), + }, + ]; + if (configContext.platform === Platform.Portal) { + buttons.push({ + key: "feedback", + iconSrc: FeedbackIcon, + label: ContainerCopyMessages.feedbackButtonLabel, + ariaLabel: ContainerCopyMessages.feedbackButtonAriaLabel, + onClick: () => {}, + }); + } + return buttons; } function btnMapper(config: CopyJobCommandBarBtnType): CommandButtonComponentProps { - return { - iconSrc: config.iconSrc, - iconAlt: config.label, - onCommandClick: config.onClick, - commandButtonLabel: undefined as string | undefined, - ariaLabel: config.ariaLabel, - tooltipText: config.label, - hasPopup: false, - disabled: config.disabled ?? false, - }; + return { + iconSrc: config.iconSrc, + iconAlt: config.label, + onCommandClick: config.onClick, + commandButtonLabel: undefined as string | undefined, + ariaLabel: config.ariaLabel, + tooltipText: config.label, + hasPopup: false, + disabled: config.disabled ?? false, + }; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars export function getCommandBarButtons(_container: Explorer): CommandButtonComponentProps[] { - return getCopyJobBtns().map(btnMapper); + return getCopyJobBtns().map(btnMapper); } - diff --git a/src/Explorer/ContainerCopy/ContainerCopyMessages.ts b/src/Explorer/ContainerCopy/ContainerCopyMessages.ts index 88fabc568..2b83ff4b3 100644 --- a/src/Explorer/ContainerCopy/ContainerCopyMessages.ts +++ b/src/Explorer/ContainerCopy/ContainerCopyMessages.ts @@ -1,117 +1,132 @@ export default { - // Copy Job Command Bar - feedbackButtonLabel: "Feedback", - feedbackButtonAriaLabel: "Provide feedback on copy jobs", - refreshButtonLabel: "Refresh", - refreshButtonAriaLabel: "Refresh copy jobs", - createCopyJobButtonLabel: "Create Copy Job", - createCopyJobButtonAriaLabel: "Create a new container copy job", + // Copy Job Command Bar + feedbackButtonLabel: "Feedback", + feedbackButtonAriaLabel: "Provide feedback on copy jobs", + refreshButtonLabel: "Refresh", + refreshButtonAriaLabel: "Refresh copy jobs", + createCopyJobButtonLabel: "Create Copy Job", + createCopyJobButtonAriaLabel: "Create a new container copy job", - // No Copy Jobs Found - noCopyJobsTitle: "No copy jobs to show", - createCopyJobButtonText: "Create a container copy job", + // No Copy Jobs Found + noCopyJobsTitle: "No copy jobs to show", + createCopyJobButtonText: "Create a container copy job", - // Create Copy Job Panel - createCopyJobPanelTitle: "Copy container", + // Create Copy Job Panel + createCopyJobPanelTitle: "Copy container", - // Select Account Screen - selectAccountDescription: "Please select a source account from which to copy.", - subscriptionDropdownLabel: "Subscription", - subscriptionDropdownPlaceholder: "Select a subscription", - sourceAccountDropdownLabel: "Account", - sourceAccountDropdownPlaceholder: "Select an account", - migrationTypeCheckboxLabel: "Copy container in offline mode", + // Select Account Screen + selectAccountDescription: "Please select a source account from which to copy.", + subscriptionDropdownLabel: "Subscription", + subscriptionDropdownPlaceholder: "Select a subscription", + sourceAccountDropdownLabel: "Account", + sourceAccountDropdownPlaceholder: "Select an account", + migrationTypeCheckboxLabel: "Copy container in offline mode", - // Select Source and Target Containers Screen - selectSourceAndTargetContainersDescription: "Please select a source container and a destination container to copy to.", - sourceContainerSubHeading: "Source container", - targetContainerSubHeading: "Destination container", - databaseDropdownLabel: "Database", - databaseDropdownPlaceholder: "Select a database", - containerDropdownLabel: "Container", - containerDropdownPlaceholder: "Select a container", + // Select Source and Target Containers Screen + selectSourceAndTargetContainersDescription: + "Please select a source container and a destination container to copy to.", + sourceContainerSubHeading: "Source container", + targetContainerSubHeading: "Destination container", + databaseDropdownLabel: "Database", + databaseDropdownPlaceholder: "Select a database", + containerDropdownLabel: "Container", + containerDropdownPlaceholder: "Select a container", - // Preview and Create Screen - jobNameLabel: "Job name", - sourceSubscriptionLabel: "Source subscription", - sourceAccountLabel: "Source account", - sourceDatabaseLabel: "Source database", - sourceContainerLabel: "Source container", - targetDatabaseLabel: "Destination database", - targetContainerLabel: "Destination container", + // Preview and Create Screen + jobNameLabel: "Job name", + sourceSubscriptionLabel: "Source subscription", + sourceAccountLabel: "Source account", + sourceDatabaseLabel: "Source database", + sourceContainerLabel: "Source container", + targetDatabaseLabel: "Destination database", + targetContainerLabel: "Destination container", - // Assign Permissions Screen - assignPermissions: { - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + // Assign Permissions Screen + assignPermissions: { + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", + }, + toggleBtn: { + onText: "On", + offText: "Off", + }, + addManagedIdentity: { + title: "System assigned managed identity enabled", + description: + "Enable a system assigned managed identity for the destination account to allow the copy job to access it.", + toggleLabel: "System assigned managed identity", + managedIdentityTooltip: + "A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.", + userAssignedIdentityTooltip: "You can select an existing user assigned identity or create a new one.", + userAssignedIdentityLabel: "You may also select a user assigned managed identity.", + createUserAssignedIdentityLink: "Create User Assigned Managed Identity", + enablementTitle: "Enable system assigned managed identity", + enablementDescription: (identityName: string) => + identityName + ? `'${identityName}' will be registered with Microsoft Entra ID. Once it is registered, '${identityName}' can be granted permissions to access resources protected by Microsoft Entra ID. Do you want to enable the system assigned managed identity for '${identityName}'?` + : "", + }, + defaultManagedIdentity: { + title: "System assigned managed identity enabled as default", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + tooltip: + "A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.", + popoverTitle: "System assigned managed identity set as default", + popoverDescription: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.", + }, + readPermissionAssigned: { + title: "Read permission assigned to default identity", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + tooltip: + "A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.", + popoverTitle: "Read permission assigned to default identity", + popoverDescription: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.", + }, + pointInTimeRestore: { + title: "Point In Time Restore enabled", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + buttonText: "Enable Point In Time Restore", + }, + onlineCopyEnabled: { + title: "Online copy enabled", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + buttonText: "Enable Online Copy", + }, + MonitorJobs: { + Columns: { + lastUpdatedTime: "Date & time", + name: "Job name", + status: "Status", + completionPercentage: "Completion %", + duration: "Duration", + error: "Error message", + mode: "Mode", + actions: "Actions", }, - toggleBtn: { - onText: "On", - offText: "Off" + Actions: { + pause: "Pause", + resume: "Resume", + cancel: "Cancel", + complete: "Complete", + viewDetails: "View Details", }, - addManagedIdentity: { - title: "System assigned managed identity enabled", - description: "Enable a system assigned managed identity for the destination account to allow the copy job to access it.", - toggleLabel: "System assigned managed identity", - managedIdentityTooltip: "A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.", - userAssignedIdentityTooltip: "You can select an existing user assigned identity or create a new one.", - userAssignedIdentityLabel: "You may also select a user assigned managed identity.", - createUserAssignedIdentityLink: "Create User Assigned Managed Identity", - enablementTitle: "Enable system assigned managed identity", - enablementDescription: (identityName: string) => identityName ? `'${identityName}' will be registered with Microsoft Entra ID. Once it is registered, '${identityName}' can be granted permissions to access resources protected by Microsoft Entra ID. Do you want to enable the system assigned managed identity for '${identityName}'?` : "", + Status: { + Pending: "Pending", + InProgress: "In Progress", + Running: "In Progress", + Partitioning: "In Progress", + Paused: "Paused", + Completed: "Completed", + Failed: "Failed", + Faulted: "Failed", + Skipped: "Cancelled", + Cancelled: "Cancelled", }, - defaultManagedIdentity: { - title: "System assigned managed identity enabled as default", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - tooltip: "A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.", - popoverTitle: "System assigned managed identity set as default", - popoverDescription: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.", - }, - readPermissionAssigned: { - title: "Read permission assigned to default identity", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - tooltip: "A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. You can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.", - popoverTitle: "Read permission assigned to default identity", - popoverDescription: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.", - }, - pointInTimeRestore: { - title: "Point In Time Restore enabled", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - buttonText: "Enable Point In Time Restore", - }, - onlineCopyEnabled: { - title: "Online copy enabled", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - buttonText: "Enable Online Copy", - }, - MonitorJobs: { - Columns: { - lastUpdatedTime: "Date & time", - name: "Job name", - status: "Status", - completionPercentage: "Completion %", - duration: "Duration", - error: "Error message", - mode: "Mode", - actions: "Actions", - }, - Actions: { - pause: "Pause", - resume: "Resume", - cancel: "Cancel", - complete: "Complete", - viewDetails: "View Details", - }, - Status: { - Pending: "Pending", - InProgress: "In Progress", - Running: "In Progress", - Partitioning: "In Progress", - Paused: "Paused", - Completed: "Completed", - Failed: "Failed", - Faulted: "Failed", - Skipped: "Cancelled", - Cancelled: "Cancelled", - } - } -} \ No newline at end of file + }, +}; diff --git a/src/Explorer/ContainerCopy/Context/CopyJobContext.tsx b/src/Explorer/ContainerCopy/Context/CopyJobContext.tsx index e3834a980..f1d28ab8a 100644 --- a/src/Explorer/ContainerCopy/Context/CopyJobContext.tsx +++ b/src/Explorer/ContainerCopy/Context/CopyJobContext.tsx @@ -5,50 +5,50 @@ import { CopyJobContextProviderType, CopyJobContextState, CopyJobFlowType } from export const CopyJobContext = React.createContext(null); export const useCopyJobContext = (): CopyJobContextProviderType => { - const context = React.useContext(CopyJobContext); - if (!context) { - throw new Error("useCopyJobContext must be used within a CopyJobContextProvider"); - } - return context; -} + const context = React.useContext(CopyJobContext); + if (!context) { + throw new Error("useCopyJobContext must be used within a CopyJobContextProvider"); + } + return context; +}; interface CopyJobContextProviderProps { - children: React.ReactNode; + children: React.ReactNode; } const getInitialCopyJobState = (): CopyJobContextState => { - return { - jobName: "", - migrationType: CopyJobMigrationType.Offline, - source: { - subscription: null, - account: null, - databaseId: "", - containerId: "", - }, - target: { - subscriptionId: userContext.subscriptionId || "", - account: userContext.databaseAccount || null, - databaseId: "", - containerId: "", - }, - sourceReadAccessFromTarget: false - } -} + return { + jobName: "", + migrationType: CopyJobMigrationType.Offline, + source: { + subscription: null, + account: null, + databaseId: "", + containerId: "", + }, + target: { + subscriptionId: userContext.subscriptionId || "", + account: userContext.databaseAccount || null, + databaseId: "", + containerId: "", + }, + sourceReadAccessFromTarget: false, + }; +}; const CopyJobContextProvider: React.FC = (props) => { - const [copyJobState, setCopyJobState] = React.useState(getInitialCopyJobState()); - const [flow, setFlow] = React.useState(null); + const [copyJobState, setCopyJobState] = React.useState(getInitialCopyJobState()); + const [flow, setFlow] = React.useState(null); - const resetCopyJobState = () => { - setCopyJobState(getInitialCopyJobState()); - } + const resetCopyJobState = () => { + setCopyJobState(getInitialCopyJobState()); + }; - return ( - - {props.children} - - ); -} + return ( + + {props.children} + + ); +}; -export default CopyJobContextProvider; \ No newline at end of file +export default CopyJobContextProvider; diff --git a/src/Explorer/ContainerCopy/CopyJobUtils.ts b/src/Explorer/ContainerCopy/CopyJobUtils.ts index 6e436ab0d..f52878971 100644 --- a/src/Explorer/ContainerCopy/CopyJobUtils.ts +++ b/src/Explorer/ContainerCopy/CopyJobUtils.ts @@ -2,89 +2,104 @@ import { DatabaseAccount } from "Contracts/DataModels"; import { CopyJobErrorType } from "./Types"; export const buildResourceLink = (resource: DatabaseAccount): string => { - const resourceId = resource.id; - // TODO: update "ms.portal.azure.com" based on environment (e.g. for PROD or Fairfax) - return `https://ms.portal.azure.com/#resource${resourceId}`; -} + const resourceId = resource.id; + // TODO: update "ms.portal.azure.com" based on environment (e.g. for PROD or Fairfax) + return `https://ms.portal.azure.com/#resource${resourceId}`; +}; export const COSMOS_SQL_COMPONENT = "CosmosDBSql"; export const COPY_JOB_API_VERSION = "2025-05-01-preview"; export function buildDataTransferJobPath({ - subscriptionId, - resourceGroup, - accountName, - jobName, - action, + subscriptionId, + resourceGroup, + accountName, + jobName, + action, }: { - subscriptionId: string; - resourceGroup: string; - accountName: string; - jobName?: string; - action?: string; + subscriptionId: string; + resourceGroup: string; + accountName: string; + jobName?: string; + action?: string; }) { - let path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`; - if (jobName) path += `/${jobName}`; - if (action) path += `/${action}`; - return path; + let path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`; + if (jobName) { + path += `/${jobName}`; + } + if (action) { + path += `/${action}`; + } + return path; } export function convertTime(timeStr: string): string | null { - const timeParts = timeStr.split(":").map(Number); + const timeParts = timeStr.split(":").map(Number); - if (timeParts.length !== 3 || timeParts.some(isNaN)) { - return null; // Return null for invalid format + if (timeParts.length !== 3 || timeParts.some(isNaN)) { + return null; // Return null for invalid format + } + const formatPart = (value: number, unit: string) => { + if (unit === "seconds") { + value = Math.round(value); } - const formatPart = (value: number, unit: string) => { - if (unit === "seconds") { - value = Math.round(value); - } - return value > 0 ? `${value.toString().padStart(2, "0")} ${unit}` : ""; - }; + return value > 0 ? `${value.toString().padStart(2, "0")} ${unit}` : ""; + }; - const [hours, minutes, seconds] = timeParts; - const formattedTimeParts = [formatPart(hours, "hours"), formatPart(minutes, "minutes"), formatPart(seconds, "seconds")] - .filter(Boolean) - .join(", "); + const [hours, minutes, seconds] = timeParts; + const formattedTimeParts = [ + formatPart(hours, "hours"), + formatPart(minutes, "minutes"), + formatPart(seconds, "seconds"), + ] + .filter(Boolean) + .join(", "); - return formattedTimeParts || "0 seconds"; // Return "0 seconds" if all parts are zero + return formattedTimeParts || "0 seconds"; // Return "0 seconds" if all parts are zero } -export function formatUTCDateTime(utcStr: string): { formattedDateTime: string, timestamp: number } | null { - const date = new Date(utcStr); - if (isNaN(date.getTime())) return null; +export function formatUTCDateTime(utcStr: string): { formattedDateTime: string; timestamp: number } | null { + const date = new Date(utcStr); + if (isNaN(date.getTime())) { + return null; + } - return { - formattedDateTime: new Intl.DateTimeFormat("en-US", { - dateStyle: "short", - timeStyle: "medium", - timeZone: "UTC", - }).format(date), - timestamp: date.getTime(), - }; + return { + formattedDateTime: new Intl.DateTimeFormat("en-US", { + dateStyle: "short", + timeStyle: "medium", + timeZone: "UTC", + }).format(date), + timestamp: date.getTime(), + }; } export function convertToCamelCase(str: string): string { - const formattedStr = str.split(/\s+/).map( - word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() - ).join(''); - return formattedStr; + const formattedStr = str + .split(/\s+/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(""); + return formattedStr; } export function extractErrorMessage(error: CopyJobErrorType): CopyJobErrorType { - return { - ...error, - message: error.message.split("\r\n\r\n")[0] - } + return { + ...error, + message: error.message.split("\r\n\r\n")[0], + }; } export function getAccountDetailsFromResourceId(accountId: string | undefined) { - if (!accountId) { - return null; - } - const pattern = new RegExp('/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\\.DocumentDB/databaseAccounts/([^/]+)', 'i'); - const matches = accountId.match(pattern); - const [_, subscriptionId, resourceGroup, accountName] = matches || []; - return { subscriptionId, resourceGroup, accountName }; -} \ No newline at end of file + if (!accountId) { + return null; + } + const pattern = new RegExp( + "/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\\.DocumentDB/databaseAccounts/([^/]+)", + "i", + ); + const matches = accountId.match(pattern); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, subscriptionId, resourceGroup, accountName] = matches || []; + return { subscriptionId, resourceGroup, accountName }; +} diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx index 24a54cecb..2b0358894 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx @@ -18,49 +18,50 @@ const textStyle = { display: "flex", alignItems: "center" }; type AddManagedIdentityProps = Partial; const AddManagedIdentity: React.FC = () => { - const { copyJobState } = useCopyJobContext(); - const [systemAssigned, onToggle] = useToggle(false); - const { loading, handleAddSystemIdentity } = useManagedIdentity(updateSystemIdentity); + const { copyJobState } = useCopyJobContext(); + const [systemAssigned, onToggle] = useToggle(false); + const { loading, handleAddSystemIdentity } = useManagedIdentity(updateSystemIdentity); - const manageIdentityLink = useMemo(() => { - const { target } = copyJobState; - const resourceUri = buildResourceLink(target.account); - return target?.account?.id ? `${resourceUri}/ManagedIdentitiesBlade` : "#"; - }, [copyJobState]); + const manageIdentityLink = useMemo(() => { + const { target } = copyJobState; + const resourceUri = buildResourceLink(target.account); + return target?.account?.id ? `${resourceUri}/ManagedIdentitiesBlade` : "#"; + }, [copyJobState]); - return ( - - - {ContainerCopyMessages.addManagedIdentity.toggleLabel}  - - } - checked={systemAssigned} - onText={ContainerCopyMessages.toggleBtn.onText} - offText={ContainerCopyMessages.toggleBtn.offText} - onChange={onToggle} - /> - - {ContainerCopyMessages.addManagedIdentity.userAssignedIdentityLabel}  - -
- - {ContainerCopyMessages.addManagedIdentity.createUserAssignedIdentityLink} - -
- onToggle(null, false)} - onPrimary={handleAddSystemIdentity} - - > - {ContainerCopyMessages.addManagedIdentity.enablementDescription(copyJobState.target?.account?.name)} - -
- ); + return ( + + + {ContainerCopyMessages.addManagedIdentity.toggleLabel}  + + + } + checked={systemAssigned} + onText={ContainerCopyMessages.toggleBtn.onText} + offText={ContainerCopyMessages.toggleBtn.offText} + onChange={onToggle} + /> + + {ContainerCopyMessages.addManagedIdentity.userAssignedIdentityLabel}  + + +
+ + {ContainerCopyMessages.addManagedIdentity.createUserAssignedIdentityLink} + +
+ onToggle(null, false)} + onPrimary={handleAddSystemIdentity} + > + {ContainerCopyMessages.addManagedIdentity.enablementDescription(copyJobState.target?.account?.name)} + +
+ ); }; export default AddManagedIdentity; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddReadPermissionToDefaultIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddReadPermissionToDefaultIdentity.tsx index 9b6a7c836..64253830d 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddReadPermissionToDefaultIdentity.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddReadPermissionToDefaultIdentity.tsx @@ -1,4 +1,4 @@ -import { ITooltipHostStyles, Stack, Toggle } from "@fluentui/react"; +import { Stack, Toggle } from "@fluentui/react"; import React, { useCallback } from "react"; import { assignRole } from "../../../../../Utils/arm/RbacUtils"; import ContainerCopyMessages from "../../../ContainerCopyMessages"; @@ -10,71 +10,71 @@ import { PermissionSectionConfig } from "./hooks/usePermissionsSection"; import useToggle from "./hooks/useToggle"; const TooltipContent = ContainerCopyMessages.readPermissionAssigned.tooltip; -const hostStyles: Partial = { root: { display: 'inline-block' } }; type AddManagedIdentityProps = Partial; const AddReadPermissionToDefaultIdentity: React.FC = () => { - const [loading, setLoading] = React.useState(false); - const { copyJobState, setCopyJobState } = useCopyJobContext(); - const [readPermissionAssigned, onToggle] = useToggle(false); + const [loading, setLoading] = React.useState(false); + const { copyJobState, setCopyJobState } = useCopyJobContext(); + const [readPermissionAssigned, onToggle] = useToggle(false); - const handleAddReadPermission = useCallback(async () => { - const { source, target } = copyJobState; - const selectedSourceAccount = source?.account; - try { - const { - subscriptionId: sourceSubscriptionId, - resourceGroup: sourceResourceGroup, - accountName: sourceAccountName - } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); + const handleAddReadPermission = useCallback(async () => { + const { source, target } = copyJobState; + const selectedSourceAccount = source?.account; + try { + const { + subscriptionId: sourceSubscriptionId, + resourceGroup: sourceResourceGroup, + accountName: sourceAccountName, + } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); - setLoading(true); - const assignedRole = await assignRole( - sourceSubscriptionId, - sourceResourceGroup, - sourceAccountName, - target?.account?.identity?.principalId!, - ); - if (assignedRole) { - setCopyJobState((prevState) => ({ - ...prevState, - sourceReadAccessFromTarget: true, - })); - } - } catch (error) { - console.error("Error assigning read permission to default identity:", error); - } finally { - setLoading(false); - } - }, [copyJobState]); + setLoading(true); + const assignedRole = await assignRole( + sourceSubscriptionId, + sourceResourceGroup, + sourceAccountName, + target?.account?.identity?.principalId ?? "", + ); + if (assignedRole) { + setCopyJobState((prevState) => ({ + ...prevState, + sourceReadAccessFromTarget: true, + })); + } + } catch (error) { + console.error("Error assigning read permission to default identity:", error); + } finally { + setLoading(false); + } + }, [copyJobState, setCopyJobState]); - return ( - -
- {ContainerCopyMessages.readPermissionAssigned.description}   -
- - onToggle(null, false)} - onPrimary={handleAddReadPermission} - > - {ContainerCopyMessages.readPermissionAssigned.popoverDescription} - -
- ); + return ( + +
+ {ContainerCopyMessages.readPermissionAssigned.description}   + +
+ + onToggle(null, false)} + onPrimary={handleAddReadPermission} + > + {ContainerCopyMessages.readPermissionAssigned.popoverDescription} + +
+ ); }; export default AddReadPermissionToDefaultIdentity; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx index 8ae19b42a..98df291ce 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx @@ -12,36 +12,37 @@ const managedIdentityTooltip = ContainerCopyMessages.defaultManagedIdentity.tool type AddManagedIdentityProps = Partial; const DefaultManagedIdentity: React.FC = () => { - const [defaultSystemAssigned, onToggle] = useToggle(false); - const { loading, handleAddSystemIdentity } = useManagedIdentity(updateDefaultIdentity); + const [defaultSystemAssigned, onToggle] = useToggle(false); + const { loading, handleAddSystemIdentity } = useManagedIdentity(updateDefaultIdentity); - return ( - -
- {ContainerCopyMessages.defaultManagedIdentity.description}   -
- - onToggle(null, false)} - onPrimary={handleAddSystemIdentity} - > - {ContainerCopyMessages.defaultManagedIdentity.popoverDescription} - -
- ); + return ( + +
+ {ContainerCopyMessages.defaultManagedIdentity.description}   + +
+ + onToggle(null, false)} + onPrimary={handleAddSystemIdentity} + > + {ContainerCopyMessages.defaultManagedIdentity.popoverDescription} + +
+ ); }; export default DefaultManagedIdentity; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx index 142d1ab4c..ba13c94ca 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx @@ -8,25 +8,21 @@ import useWindowOpenMonitor from "./hooks/useWindowOpenMonitor"; type AddManagedIdentityProps = Partial; const OnlineCopyEnabled: React.FC = () => { - const { copyJobState: { source } = {} } = useCopyJobContext(); - const sourceAccountLink = buildResourceLink(source?.account); - const onlineCopyUrl = `${sourceAccountLink}/Features`; - const onWindowClosed = () => { - console.log('Online copy window closed'); - }; - const openWindowAndMonitor = useWindowOpenMonitor(onlineCopyUrl, onWindowClosed); + const { copyJobState: { source } = {} } = useCopyJobContext(); + const sourceAccountLink = buildResourceLink(source?.account); + const onlineCopyUrl = `${sourceAccountLink}/Features`; + const onWindowClosed = () => { + // eslint-disable-next-line no-console + console.log("Online copy window closed"); + }; + const openWindowAndMonitor = useWindowOpenMonitor(onlineCopyUrl, onWindowClosed); - return ( - -
- {ContainerCopyMessages.onlineCopyEnabled.description} -
- -
- ); + return ( + +
{ContainerCopyMessages.onlineCopyEnabled.description}
+ +
+ ); }; export default OnlineCopyEnabled; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx index aa0e34d57..81e505dc0 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx @@ -9,53 +9,47 @@ import useWindowOpenMonitor from "./hooks/useWindowOpenMonitor"; type AddManagedIdentityProps = Partial; const PointInTimeRestore: React.FC = () => { - const [loading, setLoading] = useState(false); - const { copyJobState: { source } = {}, setCopyJobState } = useCopyJobContext(); - const sourceAccountLink = buildResourceLink(source?.account); - const pitrUrl = `${sourceAccountLink}/backupRestore`; + const [loading, setLoading] = useState(false); + const { copyJobState: { source } = {}, setCopyJobState } = useCopyJobContext(); + const sourceAccountLink = buildResourceLink(source?.account); + const pitrUrl = `${sourceAccountLink}/backupRestore`; - const onWindowClosed = useCallback(async () => { - try { - const selectedSourceAccount = source?.account; - const { - subscriptionId: sourceSubscriptionId, - resourceGroup: sourceResourceGroup, - accountName: sourceAccountName - } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); + const onWindowClosed = useCallback(async () => { + try { + const selectedSourceAccount = source?.account; + const { + subscriptionId: sourceSubscriptionId, + resourceGroup: sourceResourceGroup, + accountName: sourceAccountName, + } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); - setLoading(true); - const account = await fetchDatabaseAccount( - sourceSubscriptionId, - sourceResourceGroup, - sourceAccountName - ); - if (account) { - setCopyJobState((prevState) => ({ - ...prevState, - source: { ...prevState.source, account: account } - })); - } - } catch (error) { - console.error("Error fetching database account after PITR window closed:", error); - } finally { - setLoading(false); - } - }, []) - const openWindowAndMonitor = useWindowOpenMonitor(pitrUrl, onWindowClosed); + setLoading(true); + const account = await fetchDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName); + if (account) { + setCopyJobState((prevState) => ({ + ...prevState, + source: { ...prevState.source, account: account }, + })); + } + } catch (error) { + console.error("Error fetching database account after PITR window closed:", error); + } finally { + setLoading(false); + } + }, []); + const openWindowAndMonitor = useWindowOpenMonitor(pitrUrl, onWindowClosed); - return ( - -
- {ContainerCopyMessages.pointInTimeRestore.description} -
- -
- ); + return ( + +
{ContainerCopyMessages.pointInTimeRestore.description}
+ +
+ ); }; export default PointInTimeRestore; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useManagedIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useManagedIdentity.tsx index ccfa48792..08c79da3b 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useManagedIdentity.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useManagedIdentity.tsx @@ -4,53 +4,49 @@ import { useCopyJobContext } from "../../../../Context/CopyJobContext"; import { getAccountDetailsFromResourceId } from "../../../../CopyJobUtils"; interface UseManagedIdentityUpdaterParams { - updateIdentityFn: ( - subscriptionId: string, - resourceGroup?: string, - accountName?: string - ) => Promise; + updateIdentityFn: ( + subscriptionId: string, + resourceGroup?: string, + accountName?: string, + ) => Promise; } interface UseManagedIdentityUpdaterReturn { - loading: boolean; - handleAddSystemIdentity: () => Promise; + loading: boolean; + handleAddSystemIdentity: () => Promise; } const useManagedIdentity = ( - updateIdentityFn: UseManagedIdentityUpdaterParams["updateIdentityFn"] + updateIdentityFn: UseManagedIdentityUpdaterParams["updateIdentityFn"], ): UseManagedIdentityUpdaterReturn => { - const { copyJobState, setCopyJobState } = useCopyJobContext(); - const [loading, setLoading] = useState(false); + const { copyJobState, setCopyJobState } = useCopyJobContext(); + const [loading, setLoading] = useState(false); - const handleAddSystemIdentity = useCallback(async (): Promise => { - try { - setLoading(true); - const selectedTargetAccount = copyJobState?.target?.account; - const { - subscriptionId: targetSubscriptionId, - resourceGroup: targetResourceGroup, - accountName: targetAccountName - } = getAccountDetailsFromResourceId(selectedTargetAccount?.id); + const handleAddSystemIdentity = useCallback(async (): Promise => { + try { + setLoading(true); + const selectedTargetAccount = copyJobState?.target?.account; + const { + subscriptionId: targetSubscriptionId, + resourceGroup: targetResourceGroup, + accountName: targetAccountName, + } = getAccountDetailsFromResourceId(selectedTargetAccount?.id); - const updatedAccount = await updateIdentityFn( - targetSubscriptionId, - targetResourceGroup, - targetAccountName - ); - if (updatedAccount) { - setCopyJobState((prevState) => ({ - ...prevState, - target: { ...prevState.target, account: updatedAccount } - })); - } - } catch (error) { - console.error("Error enabling system-assigned managed identity:", error); - } finally { - setLoading(false); - } - }, [copyJobState, updateIdentityFn, setCopyJobState]); + const updatedAccount = await updateIdentityFn(targetSubscriptionId, targetResourceGroup, targetAccountName); + if (updatedAccount) { + setCopyJobState((prevState) => ({ + ...prevState, + target: { ...prevState.target, account: updatedAccount }, + })); + } + } catch (error) { + console.error("Error enabling system-assigned managed identity:", error); + } finally { + setLoading(false); + } + }, [copyJobState, updateIdentityFn, setCopyJobState]); - return { loading, handleAddSystemIdentity }; + return { loading, handleAddSystemIdentity }; }; -export default useManagedIdentity; \ No newline at end of file +export default useManagedIdentity; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx index 4788bde30..0f1149127 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx @@ -1,17 +1,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import { - fetchRoleAssignments, - fetchRoleDefinitions, - RoleDefinitionType -} from "../../../../../../Utils/arm/RbacUtils"; +import { fetchRoleAssignments, fetchRoleDefinitions, RoleDefinitionType } from "../../../../../../Utils/arm/RbacUtils"; import ContainerCopyMessages from "../../../../ContainerCopyMessages"; import { getAccountDetailsFromResourceId } from "../../../../CopyJobUtils"; -import { - BackupPolicyType, - CopyJobMigrationType, - DefaultIdentityType, - IdentityType -} from "../../../../Enums"; +import { BackupPolicyType, CopyJobMigrationType, DefaultIdentityType, IdentityType } from "../../../../Enums"; import { CopyJobContextState } from "../../../../Types"; import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache"; import AddManagedIdentity from "../AddManagedIdentity"; @@ -21,112 +12,110 @@ import OnlineCopyEnabled from "../OnlineCopyEnabled"; import PointInTimeRestore from "../PointInTimeRestore"; export interface PermissionSectionConfig { - id: string; - title: string; - Component: React.ComponentType - disabled: boolean; - completed?: boolean; - validate?: (state: CopyJobContextState) => boolean | Promise; + id: string; + title: string; + Component: React.ComponentType; + disabled: boolean; + completed?: boolean; + validate?: (state: CopyJobContextState) => boolean | Promise; } // Section IDs for maintainability export const SECTION_IDS = { - addManagedIdentity: "addManagedIdentity", - defaultManagedIdentity: "defaultManagedIdentity", - readPermissionAssigned: "readPermissionAssigned", - pointInTimeRestore: "pointInTimeRestore", - onlineCopyEnabled: "onlineCopyEnabled" + addManagedIdentity: "addManagedIdentity", + defaultManagedIdentity: "defaultManagedIdentity", + readPermissionAssigned: "readPermissionAssigned", + pointInTimeRestore: "pointInTimeRestore", + onlineCopyEnabled: "onlineCopyEnabled", } as const; const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [ - { - id: SECTION_IDS.addManagedIdentity, - title: ContainerCopyMessages.addManagedIdentity.title, - Component: AddManagedIdentity, - disabled: true, - validate: (state: CopyJobContextState) => { - const targetAccountIdentityType = (state?.target?.account?.identity?.type ?? "").toLowerCase(); - return ( - targetAccountIdentityType === IdentityType.SystemAssigned || - targetAccountIdentityType === IdentityType.UserAssigned - ); - } + { + id: SECTION_IDS.addManagedIdentity, + title: ContainerCopyMessages.addManagedIdentity.title, + Component: AddManagedIdentity, + disabled: true, + validate: (state: CopyJobContextState) => { + const targetAccountIdentityType = (state?.target?.account?.identity?.type ?? "").toLowerCase(); + return ( + targetAccountIdentityType === IdentityType.SystemAssigned || + targetAccountIdentityType === IdentityType.UserAssigned + ); }, - { - id: SECTION_IDS.defaultManagedIdentity, - title: ContainerCopyMessages.defaultManagedIdentity.title, - Component: DefaultManagedIdentity, - disabled: true, - validate: (state: CopyJobContextState) => { - const targetAccountDefaultIdentity = (state?.target?.account?.properties?.defaultIdentity ?? "").toLowerCase(); - return targetAccountDefaultIdentity === DefaultIdentityType.SystemAssignedIdentity; - } + }, + { + id: SECTION_IDS.defaultManagedIdentity, + title: ContainerCopyMessages.defaultManagedIdentity.title, + Component: DefaultManagedIdentity, + disabled: true, + validate: (state: CopyJobContextState) => { + const targetAccountDefaultIdentity = (state?.target?.account?.properties?.defaultIdentity ?? "").toLowerCase(); + return targetAccountDefaultIdentity === DefaultIdentityType.SystemAssignedIdentity; }, - { - id: SECTION_IDS.readPermissionAssigned, - title: ContainerCopyMessages.readPermissionAssigned.title, - Component: AddReadPermissionToDefaultIdentity, - disabled: true, - validate: async (state: CopyJobContextState) => { - const principalId = state?.target?.account?.identity?.principalId; - const selectedSourceAccount = state?.source?.account; - const { - subscriptionId: sourceSubscriptionId, - resourceGroup: sourceResourceGroup, - accountName: sourceAccountName - } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); + }, + { + id: SECTION_IDS.readPermissionAssigned, + title: ContainerCopyMessages.readPermissionAssigned.title, + Component: AddReadPermissionToDefaultIdentity, + disabled: true, + validate: async (state: CopyJobContextState) => { + const principalId = state?.target?.account?.identity?.principalId; + const selectedSourceAccount = state?.source?.account; + const { + subscriptionId: sourceSubscriptionId, + resourceGroup: sourceResourceGroup, + accountName: sourceAccountName, + } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); - const rolesAssigned = await fetchRoleAssignments( - sourceSubscriptionId, - sourceResourceGroup, - sourceAccountName, - principalId - ); + const rolesAssigned = await fetchRoleAssignments( + sourceSubscriptionId, + sourceResourceGroup, + sourceAccountName, + principalId, + ); - const roleDefinitions = await fetchRoleDefinitions( - rolesAssigned ?? [] - ); - return checkTargetHasReaderRoleOnSource(roleDefinitions ?? []); - } - } + const roleDefinitions = await fetchRoleDefinitions(rolesAssigned ?? []); + return checkTargetHasReaderRoleOnSource(roleDefinitions ?? []); + }, + }, ]; const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [ - { - id: SECTION_IDS.pointInTimeRestore, - title: ContainerCopyMessages.pointInTimeRestore.title, - Component: PointInTimeRestore, - disabled: true, - validate: (state: CopyJobContextState) => { - const sourceAccountBackupPolicy = state?.source?.account?.properties?.backupPolicy?.type ?? ""; - return sourceAccountBackupPolicy === BackupPolicyType.Continuous; - } + { + id: SECTION_IDS.pointInTimeRestore, + title: ContainerCopyMessages.pointInTimeRestore.title, + Component: PointInTimeRestore, + disabled: true, + validate: (state: CopyJobContextState) => { + const sourceAccountBackupPolicy = state?.source?.account?.properties?.backupPolicy?.type ?? ""; + return sourceAccountBackupPolicy === BackupPolicyType.Continuous; }, - { - id: SECTION_IDS.onlineCopyEnabled, - title: ContainerCopyMessages.onlineCopyEnabled.title, - Component: OnlineCopyEnabled, - disabled: true, - validate: (_state: CopyJobContextState) => { - return false; - } - } + }, + { + id: SECTION_IDS.onlineCopyEnabled, + title: ContainerCopyMessages.onlineCopyEnabled.title, + Component: OnlineCopyEnabled, + disabled: true, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + validate: (_state: CopyJobContextState) => { + return false; + }, + }, ]; - /** * Checks if the user has the Reader role based on role definitions. */ export function checkTargetHasReaderRoleOnSource(roleDefinitions: RoleDefinitionType[]): boolean { - return roleDefinitions?.some( - role => - role.name === "00000000-0000-0000-0000-000000000001" || - role.permissions.some( - permission => - permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata") && - permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read") - ) - ); + return roleDefinitions?.some( + (role) => + role.name === "00000000-0000-0000-0000-000000000001" || + role.permissions.some( + (permission) => + permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata") && + permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read"), + ), + ); } /** @@ -134,75 +123,76 @@ export function checkTargetHasReaderRoleOnSource(roleDefinitions: RoleDefinition * Memoizes derived values for performance and decouples logic for testability. */ const usePermissionSections = (state: CopyJobContextState): PermissionSectionConfig[] => { - const { validationCache, setValidationCache } = useCopyJobPrerequisitesCache(); - const [permissionSections, setPermissionSections] = useState(null); - const isValidatingRef = useRef(false); + const { validationCache, setValidationCache } = useCopyJobPrerequisitesCache(); + const [permissionSections, setPermissionSections] = useState(null); + const isValidatingRef = useRef(false); - const sectionToValidate = useMemo(() => { - const baseSections = [...PERMISSION_SECTIONS_CONFIG]; - if (state.migrationType === CopyJobMigrationType.Online) { - return [...baseSections, ...PERMISSION_SECTIONS_FOR_ONLINE_JOBS]; + const sectionToValidate = useMemo(() => { + const baseSections = [...PERMISSION_SECTIONS_CONFIG]; + if (state.migrationType === CopyJobMigrationType.Online) { + return [...baseSections, ...PERMISSION_SECTIONS_FOR_ONLINE_JOBS]; + } + return baseSections; + }, [state.migrationType]); + + const memoizedValidationCache = useMemo(() => { + if (state.migrationType === CopyJobMigrationType.Offline) { + validationCache.delete(SECTION_IDS.pointInTimeRestore); + validationCache.delete(SECTION_IDS.onlineCopyEnabled); + } + return validationCache; + }, [state.migrationType]); + + useEffect(() => { + const validateSections = async () => { + if (isValidatingRef.current) { + return; + } + + isValidatingRef.current = true; + const result: PermissionSectionConfig[] = []; + const newValidationCache = new Map(memoizedValidationCache); + + for (let i = 0; i < sectionToValidate.length; i++) { + const section = sectionToValidate[i]; + + // Check if this section was already validated and passed + if (newValidationCache.has(section.id) && newValidationCache.get(section.id) === true) { + result.push({ ...section, completed: true }); + continue; } - return baseSections; - }, [state.migrationType]); - - const memoizedValidationCache = useMemo(() => { - if (state.migrationType === CopyJobMigrationType.Offline) { - validationCache.delete(SECTION_IDS.pointInTimeRestore); - validationCache.delete(SECTION_IDS.onlineCopyEnabled); - } - return validationCache; - }, [state.migrationType]); - - useEffect(() => { - const validateSections = async () => { - if (isValidatingRef.current) return; - - isValidatingRef.current = true; - const result: PermissionSectionConfig[] = []; - const newValidationCache = new Map(memoizedValidationCache); - - for (let i = 0; i < sectionToValidate.length; i++) { - const section = sectionToValidate[i]; - - // Check if this section was already validated and passed - if (newValidationCache.has(section.id) && newValidationCache.get(section.id) === true) { - result.push({ ...section, completed: true }); - continue; - } - // We've reached the first non-cached section - validate it - if (section.validate) { - const isValid = await section.validate(state); - newValidationCache.set(section.id, isValid); - result.push({ ...section, completed: isValid }); - // Stop validation if current section failed - if (!isValid) { - for (let j = i + 1; j < sectionToValidate.length; j++) { - result.push({ ...sectionToValidate[j], completed: false }); - } - break; - } - } - else { - // Section has no validate method - newValidationCache.set(section.id, false); - result.push({ ...section, completed: false }); - } + // We've reached the first non-cached section - validate it + if (section.validate) { + const isValid = await section.validate(state); + newValidationCache.set(section.id, isValid); + result.push({ ...section, completed: isValid }); + // Stop validation if current section failed + if (!isValid) { + for (let j = i + 1; j < sectionToValidate.length; j++) { + result.push({ ...sectionToValidate[j], completed: false }); } - - setValidationCache(newValidationCache); - setPermissionSections(result); - isValidatingRef.current = false; + break; + } + } else { + // Section has no validate method + newValidationCache.set(section.id, false); + result.push({ ...section, completed: false }); } + } - validateSections(); + setValidationCache(newValidationCache); + setPermissionSections(result); + isValidatingRef.current = false; + }; - return () => { - isValidatingRef.current = false; - } - }, [state, sectionToValidate]); + validateSections(); - return permissionSections ?? []; + return () => { + isValidatingRef.current = false; + }; + }, [state, sectionToValidate]); + + return permissionSections ?? []; }; -export default usePermissionSections; \ No newline at end of file +export default usePermissionSections; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx index 3c995eafc..b8f971595 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx @@ -1,11 +1,11 @@ import { useCallback, useState } from "react"; const useToggle = (initialState = false) => { - const [state, setState] = useState(initialState); - const onToggle = useCallback((_, checked?: boolean) => { - setState(!!checked); - }, []); - return [state, onToggle] as const; + const [state, setState] = useState(initialState); + const onToggle = useCallback((_, checked?: boolean) => { + setState(!!checked); + }, []); + return [state, onToggle] as const; }; -export default useToggle; \ No newline at end of file +export default useToggle; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx index ce4daab34..0d72d397a 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx @@ -1,32 +1,31 @@ import { useEffect, useRef } from "react"; const useWindowOpenMonitor = (url: string, onClose?: () => void, intervalMs = 500) => { - const intervalRef = useRef(null); + const intervalRef = useRef(null); - const openWindowAndMonitor = () => { - const newWindow = window.open(url, '_blank'); - intervalRef.current = setInterval(() => { - if (newWindow?.closed) { - clearInterval(intervalRef.current!); - intervalRef.current = null; - console.log('New window has been closed!'); - if (onClose) { - onClose(); - } - } - }, intervalMs); + const openWindowAndMonitor = () => { + const newWindow = window.open(url, "_blank"); + intervalRef.current = setInterval(() => { + if (newWindow?.closed) { + clearInterval(intervalRef.current!); + intervalRef.current = null; + if (onClose) { + onClose(); + } + } + }, intervalMs); + }; + + useEffect(() => { + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } }; + }, []); - useEffect(() => { - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }; - }, []); - - return openWindowAndMonitor; + return openWindowAndMonitor; }; -export default useWindowOpenMonitor; \ No newline at end of file +export default useWindowOpenMonitor; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx index 4cec74262..19bd46281 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx @@ -1,10 +1,5 @@ import { Image, Stack, Text } from "@fluentui/react"; -import { - Accordion, - AccordionHeader, - AccordionItem, - AccordionPanel -} from "@fluentui/react-components"; +import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from "@fluentui/react-components"; import React, { useEffect } from "react"; import CheckmarkIcon from "../../../../../../images/successfulPopup.svg"; import WarningIcon from "../../../../../../images/warning.svg"; @@ -14,72 +9,58 @@ import { useCopyJobContext } from "../../../Context/CopyJobContext"; import { CopyJobMigrationType } from "../../../Enums"; import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection"; -const PermissionSection: React.FC = ({ - id, - title, - Component, - completed, - disabled -}) => ( - - - {title} - {completed - - - - - +const PermissionSection: React.FC = ({ id, title, Component, completed, disabled }) => ( + + + + {title} + + {completed + + + + + ); const AssignPermissions = () => { - const { copyJobState } = useCopyJobContext(); - const permissionSections = usePermissionSections(copyJobState); - const [openItems, setOpenItems] = React.useState([]); + const { copyJobState } = useCopyJobContext(); + const permissionSections = usePermissionSections(copyJobState); + const [openItems, setOpenItems] = React.useState([]); - const indentLevels = React.useMemo( - () => Array(copyJobState.migrationType === CopyJobMigrationType.Online ? 5 : 3).fill({ level: 0, width: "100%" }), - [] - ); + const indentLevels = React.useMemo( + () => Array(copyJobState.migrationType === CopyJobMigrationType.Online ? 5 : 3).fill({ level: 0, width: "100%" }), + [], + ); - useEffect(() => { - const firstIncompleteSection = permissionSections.find(section => !section.completed); - const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : []; - if (JSON.stringify(openItems) !== JSON.stringify(nextOpenItems)) { - setOpenItems(nextOpenItems); - } - }, [permissionSections]); + useEffect(() => { + const firstIncompleteSection = permissionSections.find((section) => !section.completed); + const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : []; + if (JSON.stringify(openItems) !== JSON.stringify(nextOpenItems)) { + setOpenItems(nextOpenItems); + } + }, [permissionSections]); - return ( - - - {ContainerCopyMessages.assignPermissions.description} - - { - permissionSections?.length === 0 ? ( - - ) : ( - - { - permissionSections.map(section => ( - - )) - } - - ) - } - - ); + return ( + + {ContainerCopyMessages.assignPermissions.description} + {permissionSections?.length === 0 ? ( + + ) : ( + + {permissionSections.map((section) => ( + + ))} + + )} + + ); }; -export default AssignPermissions; \ No newline at end of file +export default AssignPermissions; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow.tsx index 02f5569d2..fefcccc66 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow.tsx @@ -2,26 +2,24 @@ import { Stack } from "@fluentui/react"; import React from "react"; interface FieldRowProps { - label?: string; - children: React.ReactNode; - labelClassName?: string; + label?: string; + children: React.ReactNode; + labelClassName?: string; } const FieldRow: React.FC = ({ label = "", children, labelClassName = "" }) => { - return ( - - { - label && ( - - - - ) - } - - {children} - - - ); + return ( + + {label && ( + + + + )} + + {children} + + + ); }; export default FieldRow; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx index 1d6f9808f..0187c5ec7 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx @@ -3,13 +3,15 @@ import React from "react"; import InfoIcon from "../../../../../../images/Info.svg"; const InfoTooltip: React.FC<{ content?: string }> = ({ content }) => { - if (!content) return null; - const hostStyles: Partial = { root: { display: 'inline-block' } }; - return ( - - Information - - ); + if (!content) { + return null; + } + const hostStyles: Partial = { root: { display: "inline-block" } }; + return ( + + Information + + ); }; -export default React.memo(InfoTooltip); \ No newline at end of file +export default React.memo(InfoTooltip); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/NavigationControls.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/NavigationControls.tsx index 776b2ece4..188c7d352 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/NavigationControls.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/NavigationControls.tsx @@ -2,27 +2,27 @@ import { DefaultButton, PrimaryButton, Stack } from "@fluentui/react"; import React from "react"; type NavigationControlsProps = { - primaryBtnText: string; - onPrimary: () => void; - onPrevious: () => void; - onCancel: () => void; - isPrimaryDisabled: boolean; - isPreviousDisabled: boolean; + primaryBtnText: string; + onPrimary: () => void; + onPrevious: () => void; + onCancel: () => void; + isPrimaryDisabled: boolean; + isPreviousDisabled: boolean; }; const NavigationControls: React.FC = ({ - primaryBtnText, - onPrimary, - onPrevious, - onCancel, - isPrimaryDisabled, - isPreviousDisabled, + primaryBtnText, + onPrimary, + onPrevious, + onCancel, + isPrimaryDisabled, + isPreviousDisabled, }) => ( - - - - - + + + + + ); -export default React.memo(NavigationControls); \ No newline at end of file +export default React.memo(NavigationControls); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx index 097178634..eec4f5402 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx @@ -1,48 +1,67 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/display-name */ import { DefaultButton, PrimaryButton, Stack, Text } from "@fluentui/react"; import React from "react"; interface PopoverContainerProps { - isLoading?: boolean; - title?: string; - children?: React.ReactNode; - onPrimary: () => void; - onCancel: () => void; + isLoading?: boolean; + title?: string; + children?: React.ReactNode; + onPrimary: () => void; + onCancel: () => void; } -const PopoverContainer: React.FC = React.memo(({ isLoading = false, title, children, onPrimary, onCancel }) => { +const PopoverContainer: React.FC = React.memo( + ({ isLoading = false, title, children, onPrimary, onCancel }) => { return ( - - {title} - {children} - - - - + + + {title} + + {children} + + + + ); -}); + }, +); interface PopoverMessageProps { - isLoading?: boolean; - visible: boolean; - title: string; - onCancel: () => void; - onPrimary: () => void; - children: React.ReactNode; + isLoading?: boolean; + visible: boolean; + title: string; + onCancel: () => void; + onPrimary: () => void; + children: React.ReactNode; } -const PopoverMessage: React.FC = ({ isLoading = false, visible, title, onCancel, onPrimary, children }) => { - if (!visible) return null; - return ( - - {children} - - ); +const PopoverMessage: React.FC = ({ + isLoading = false, + visible, + title, + onCancel, + onPrimary, + children, +}) => { + if (!visible) { + return null; + } + return ( + + {children} + + ); }; export default PopoverMessage; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreens.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreens.tsx index 1a26fe526..dbc8cbd80 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreens.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreens.tsx @@ -4,31 +4,31 @@ import { useCopyJobNavigation } from "../Utils/useCopyJobNavigation"; import NavigationControls from "./Components/NavigationControls"; const CreateCopyJobScreens: React.FC = () => { - const { - currentScreen, - isPrimaryDisabled, - isPreviousDisabled, - handlePrimary, - handlePrevious, - handleCancel, - primaryBtnText - } = useCopyJobNavigation(); + const { + currentScreen, + isPrimaryDisabled, + isPreviousDisabled, + handlePrimary, + handlePrevious, + handleCancel, + primaryBtnText, + } = useCopyJobNavigation(); - return ( - - {currentScreen?.component} - - - - - ); + return ( + + {currentScreen?.component} + + + + + ); }; export default CreateCopyJobScreens; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreensProvider.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreensProvider.tsx index ecf326226..3436164e9 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreensProvider.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/CreateCopyJobScreensProvider.tsx @@ -3,11 +3,11 @@ import CopyJobContextProvider from "../../Context/CopyJobContext"; import CreateCopyJobScreens from "./CreateCopyJobScreens"; const CreateCopyJobScreensProvider = () => { - return ( - - - - ); + return ( + + + + ); }; -export default CreateCopyJobScreensProvider; \ No newline at end of file +export default CreateCopyJobScreensProvider; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/Utils/PreviewCopyJobUtils.ts b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/Utils/PreviewCopyJobUtils.ts index 51e197aed..814073767 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/Utils/PreviewCopyJobUtils.ts +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/Utils/PreviewCopyJobUtils.ts @@ -2,42 +2,42 @@ import { IColumn } from "@fluentui/react"; import ContainerCopyMessages from "../../../../ContainerCopyMessages"; const commonProps = { - minWidth: 130, - maxWidth: 140, - styles: { - root: { - whiteSpace: 'normal', - lineHeight: '1.2', - wordBreak: 'break-word' - } - } + minWidth: 130, + maxWidth: 140, + styles: { + root: { + whiteSpace: "normal", + lineHeight: "1.2", + wordBreak: "break-word", + }, + }, }; -export const getPreviewCopyJobDetailsListColumns = function (): IColumn[] { - return [ - { - key: 'sourcedbname', - name: ContainerCopyMessages.sourceDatabaseLabel, - fieldName: 'sourceDatabaseName', - ...commonProps - }, - { - key: 'sourcecolname', - name: ContainerCopyMessages.sourceContainerLabel, - fieldName: 'sourceContainerName', - ...commonProps - }, - { - key: 'targetdbname', - name: ContainerCopyMessages.targetDatabaseLabel, - fieldName: 'targetDatabaseName', - ...commonProps - }, - { - key: 'targetcolname', - name: ContainerCopyMessages.targetContainerLabel, - fieldName: 'targetContainerName', - ...commonProps - } - ]; +export const getPreviewCopyJobDetailsListColumns = (): IColumn[] => { + return [ + { + key: "sourcedbname", + name: ContainerCopyMessages.sourceDatabaseLabel, + fieldName: "sourceDatabaseName", + ...commonProps, + }, + { + key: "sourcecolname", + name: ContainerCopyMessages.sourceContainerLabel, + fieldName: "sourceContainerName", + ...commonProps, + }, + { + key: "targetdbname", + name: ContainerCopyMessages.targetDatabaseLabel, + fieldName: "targetDatabaseName", + ...commonProps, + }, + { + key: "targetcolname", + name: ContainerCopyMessages.targetContainerLabel, + fieldName: "targetContainerName", + ...commonProps, + }, + ]; }; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/index.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/index.tsx index 98401c047..c270ccdf5 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/index.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/PreviewCopyJob/index.tsx @@ -1,53 +1,52 @@ -import { DetailsList, DetailsListLayoutMode, Stack, Text, TextField } from '@fluentui/react'; -import FieldRow from 'Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow'; -import React from 'react'; -import ContainerCopyMessages from '../../../ContainerCopyMessages'; -import { useCopyJobContext } from '../../../Context/CopyJobContext'; -import { getPreviewCopyJobDetailsListColumns } from './Utils/PreviewCopyJobUtils'; +import { DetailsList, DetailsListLayoutMode, Stack, Text, TextField } from "@fluentui/react"; +import FieldRow from "Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow"; +import React from "react"; +import ContainerCopyMessages from "../../../ContainerCopyMessages"; +import { useCopyJobContext } from "../../../Context/CopyJobContext"; +import { getPreviewCopyJobDetailsListColumns } from "./Utils/PreviewCopyJobUtils"; const PreviewCopyJob: React.FC = () => { - const { copyJobState, setCopyJobState } = useCopyJobContext(); + const { copyJobState, setCopyJobState } = useCopyJobContext(); - const selectedDatabaseAndContainers = [{ - sourceDatabaseName: copyJobState.source?.databaseId, - sourceContainerName: copyJobState.source?.containerId, - targetDatabaseName: copyJobState.target?.databaseId, - targetContainerName: copyJobState.target?.containerId, - }]; - const jobName = copyJobState.jobName; + const selectedDatabaseAndContainers = [ + { + sourceDatabaseName: copyJobState.source?.databaseId, + sourceContainerName: copyJobState.source?.containerId, + targetDatabaseName: copyJobState.target?.databaseId, + targetContainerName: copyJobState.target?.containerId, + }, + ]; + const jobName = copyJobState.jobName; - const onJobNameChange = (_ev?: React.FormEvent, newValue?: string) => { - setCopyJobState((prevState) => ({ - ...prevState, - jobName: newValue || '', - })); - }; - return ( - - - - - - {ContainerCopyMessages.sourceSubscriptionLabel} - {copyJobState.source?.subscription?.displayName} - - - {ContainerCopyMessages.sourceAccountLabel} - {copyJobState.source?.account?.name} - - - - - - ); + const onJobNameChange = (_ev?: React.FormEvent, newValue?: string) => { + setCopyJobState((prevState) => ({ + ...prevState, + jobName: newValue || "", + })); + }; + return ( + + + + + + {ContainerCopyMessages.sourceSubscriptionLabel} + {copyJobState.source?.subscription?.displayName} + + + {ContainerCopyMessages.sourceAccountLabel} + {copyJobState.source?.account?.name} + + + + + + ); }; export default PreviewCopyJob; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/AccountDropdown.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/AccountDropdown.tsx index c499259cb..4b6072ff8 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/AccountDropdown.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/AccountDropdown.tsx @@ -1,3 +1,5 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/display-name */ import { Dropdown } from "@fluentui/react"; import React from "react"; import ContainerCopyMessages from "../../../../ContainerCopyMessages"; @@ -5,24 +7,24 @@ import { DropdownOptionType } from "../../../../Types"; import FieldRow from "../../Components/FieldRow"; interface AccountDropdownProps { - options: DropdownOptionType[]; - selectedKey?: string; - disabled: boolean; - onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void; + options: DropdownOptionType[]; + selectedKey?: string; + disabled: boolean; + onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void; } export const AccountDropdown: React.FC = React.memo( - ({ options, selectedKey, disabled, onChange }) => ( - - - - ) -); \ No newline at end of file + ({ options, selectedKey, disabled, onChange }) => ( + + + + ), +); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx index ba081bfdd..a72965fc6 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx @@ -1,20 +1,16 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/display-name */ import { Checkbox, Stack } from "@fluentui/react"; import React from "react"; import ContainerCopyMessages from "../../../../ContainerCopyMessages"; interface MigrationTypeCheckboxProps { - checked: boolean; - onChange: (_ev?: React.FormEvent, checked?: boolean) => void; + checked: boolean; + onChange: (_ev?: React.FormEvent, checked?: boolean) => void; } -export const MigrationTypeCheckbox: React.FC = React.memo( - ({ checked, onChange }) => ( - - - - ) -); \ No newline at end of file +export const MigrationTypeCheckbox: React.FC = React.memo(({ checked, onChange }) => ( + + + +)); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/SubscriptionDropdown.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/SubscriptionDropdown.tsx index 8c511d9aa..64d2c6858 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/SubscriptionDropdown.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/SubscriptionDropdown.tsx @@ -1,3 +1,5 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/display-name */ import { Dropdown } from "@fluentui/react"; import React from "react"; import ContainerCopyMessages from "../../../../ContainerCopyMessages"; @@ -5,22 +7,22 @@ import { DropdownOptionType } from "../../../../Types"; import FieldRow from "../../Components/FieldRow"; interface SubscriptionDropdownProps { - options: DropdownOptionType[]; - selectedKey?: string; - onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void; + options: DropdownOptionType[]; + selectedKey?: string; + onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void; } export const SubscriptionDropdown: React.FC = React.memo( - ({ options, selectedKey, onChange }) => ( - - - - ) -); \ No newline at end of file + ({ options, selectedKey, onChange }) => ( + + + + ), +); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx index 46c778d08..a8697db77 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx @@ -4,75 +4,75 @@ import { CopyJobMigrationType } from "../../../../Enums"; import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } from "../../../../Types"; export function useDropdownOptions( - subscriptions: Subscription[], - accounts: DatabaseAccount[] + subscriptions: Subscription[], + accounts: DatabaseAccount[], ): { - subscriptionOptions: DropdownOptionType[], - accountOptions: DropdownOptionType[] + subscriptionOptions: DropdownOptionType[]; + accountOptions: DropdownOptionType[]; } { - const subscriptionOptions = React.useMemo( - () => - subscriptions?.map((sub) => ({ - key: sub.subscriptionId, - text: sub.displayName, - data: sub, - })) || [], - [subscriptions] - ); + const subscriptionOptions = React.useMemo( + () => + subscriptions?.map((sub) => ({ + key: sub.subscriptionId, + text: sub.displayName, + data: sub, + })) || [], + [subscriptions], + ); - const accountOptions = React.useMemo( - () => - accounts?.map((account) => ({ - key: account.id, - text: account.name, - data: account, - })) || [], - [accounts] - ); + const accountOptions = React.useMemo( + () => + accounts?.map((account) => ({ + key: account.id, + text: account.name, + data: account, + })) || [], + [accounts], + ); - return { subscriptionOptions, accountOptions }; + return { subscriptionOptions, accountOptions }; } type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"]; export function useEventHandlers(setCopyJobState: setCopyJobStateType) { - const handleSelectSourceAccount = React.useCallback( - (type: "subscription" | "account", data: Subscription & DatabaseAccount | undefined) => { - setCopyJobState((prevState: CopyJobContextState) => { - if (type === "subscription") { - return { - ...prevState, - source: { - ...prevState.source, - subscription: data || null, - account: null, // reset on subscription change - }, - }; - } - if (type === "account") { - return { - ...prevState, - source: { - ...prevState.source, - account: data || null, - }, - }; - } - return prevState; - }); - }, - [setCopyJobState] - ); + const handleSelectSourceAccount = React.useCallback( + (type: "subscription" | "account", data: (Subscription & DatabaseAccount) | undefined) => { + setCopyJobState((prevState: CopyJobContextState) => { + if (type === "subscription") { + return { + ...prevState, + source: { + ...prevState.source, + subscription: data || null, + account: null, // reset on subscription change + }, + }; + } + if (type === "account") { + return { + ...prevState, + source: { + ...prevState.source, + account: data || null, + }, + }; + } + return prevState; + }); + }, + [setCopyJobState], + ); - const handleMigrationTypeChange = React.useCallback( - (_ev?: React.FormEvent, checked?: boolean) => { - setCopyJobState((prevState: CopyJobContextState) => ({ - ...prevState, - migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online, - })); - }, - [setCopyJobState] - ); + const handleMigrationTypeChange = React.useCallback( + (_ev?: React.FormEvent, checked?: boolean) => { + setCopyJobState((prevState: CopyJobContextState) => ({ + ...prevState, + migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online, + })); + }, + [setCopyJobState], + ); - return { handleSelectSourceAccount, handleMigrationTypeChange }; + return { handleSelectSourceAccount, handleMigrationTypeChange }; } diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx index 0dd4cdc73..0fe6e3257 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/display-name */ import { Stack } from "@fluentui/react"; import React from "react"; import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels"; @@ -11,46 +12,41 @@ import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox"; import { SubscriptionDropdown } from "./Components/SubscriptionDropdown"; import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils"; -interface SelectAccountProps { } +const SelectAccount = React.memo(() => { + const { copyJobState, setCopyJobState } = useCopyJobContext(); + const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId; -const SelectAccount = React.memo( - (_props: SelectAccountProps) => { - const { copyJobState, setCopyJobState } = useCopyJobContext(); - const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId; + const subscriptions: Subscription[] = useSubscriptions(); + const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId); + const sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter( + (account) => account.type === "SQL" || account.kind === "GlobalDocumentDB", + ); - const subscriptions: Subscription[] = useSubscriptions(); - const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId); - const sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter(account => account.type === "SQL" || account.kind === "GlobalDocumentDB"); + const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts); + const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState); - const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts); - const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState); + const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline; - const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline; + return ( + + {ContainerCopyMessages.selectAccountDescription} - return ( - - {ContainerCopyMessages.selectAccountDescription} + handleSelectSourceAccount("subscription", option?.data)} + /> - handleSelectSourceAccount("subscription", option?.data)} - /> + handleSelectSourceAccount("account", option?.data)} + /> - handleSelectSourceAccount("account", option?.data)} - /> - - - - ); - } -); + + + ); +}); export default SelectAccount; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/Events/DropDownChangeHandler.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/Events/DropDownChangeHandler.tsx index 94037c4bf..7e7ef68b9 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/Events/DropDownChangeHandler.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/Events/DropDownChangeHandler.tsx @@ -1,36 +1,35 @@ +import React from "react"; import { CopyJobContextState, DropdownOptionType } from "../../../../Types"; -export function dropDownChangeHandler( - setCopyJobState: React.Dispatch> -) { - return (type: "sourceDatabase" | "sourceContainer" | "targetDatabase" | "targetContainer") => - (_evnt: any, option: DropdownOptionType) => { - const value = option.key; - setCopyJobState((prevState) => { - switch (type) { - case "sourceDatabase": - return { - ...prevState, - source: { ...prevState.source, databaseId: value, containerId: undefined } - }; - case "sourceContainer": - return { - ...prevState, - source: { ...prevState.source, containerId: value } - }; - case "targetDatabase": - return { - ...prevState, - target: { ...prevState.target, databaseId: value, containerId: undefined } - }; - case "targetContainer": - return { - ...prevState, - target: { ...prevState.target, containerId: value } - }; - default: - return prevState; - } - }); +export function dropDownChangeHandler(setCopyJobState: React.Dispatch>) { + return (type: "sourceDatabase" | "sourceContainer" | "targetDatabase" | "targetContainer") => + (_evnt: React.FormEvent, option: DropdownOptionType) => { + const value = option.key; + setCopyJobState((prevState) => { + switch (type) { + case "sourceDatabase": + return { + ...prevState, + source: { ...prevState.source, databaseId: value, containerId: undefined }, + }; + case "sourceContainer": + return { + ...prevState, + source: { ...prevState.source, containerId: value }, + }; + case "targetDatabase": + return { + ...prevState, + target: { ...prevState.target, databaseId: value, containerId: undefined }, + }; + case "targetContainer": + return { + ...prevState, + target: { ...prevState.target, containerId: value }, + }; + default: + return prevState; } + }); + }; } diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/components/DatabaseContainerSection.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/components/DatabaseContainerSection.tsx index 8bb4c61d6..6070d324d 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/components/DatabaseContainerSection.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/components/DatabaseContainerSection.tsx @@ -5,39 +5,39 @@ import { DatabaseContainerSectionProps } from "../../../../Types"; import FieldRow from "../../Components/FieldRow"; export const DatabaseContainerSection = ({ - heading, - databaseOptions, - selectedDatabase, - databaseDisabled, - databaseOnChange, - containerOptions, - selectedContainer, - containerDisabled, - containerOnChange + heading, + databaseOptions, + selectedDatabase, + databaseDisabled, + databaseOnChange, + containerOptions, + selectedContainer, + containerDisabled, + containerOnChange, }: DatabaseContainerSectionProps) => ( - - - - - - - - - + + + + + + + + + ); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/index.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/index.tsx index e6435205d..07dcf5c14 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/index.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/index.tsx @@ -1,4 +1,5 @@ import { Stack } from "@fluentui/react"; +import { DatabaseModel } from "Contracts/DataModels"; import React from "react"; import { useDatabases } from "../../../../../hooks/useDatabases"; import { useDataContainers } from "../../../../../hooks/useDataContainers"; @@ -8,73 +9,64 @@ import { DatabaseContainerSection } from "./components/DatabaseContainerSection" import { dropDownChangeHandler } from "./Events/DropDownChangeHandler"; import { useMemoizedSourceAndTargetData } from "./memoizedData"; -interface SelectSourceAndTargetContainersProps { } +const SelectSourceAndTargetContainers = () => { + const { copyJobState, setCopyJobState } = useCopyJobContext(); + const { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams } = + useMemoizedSourceAndTargetData(copyJobState); -const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainersProps) => { - const { copyJobState, setCopyJobState } = useCopyJobContext(); - const { - source, - target, - sourceDbParams, - sourceContainerParams, - targetDbParams, - targetContainerParams - } = useMemoizedSourceAndTargetData(copyJobState); + // Custom hooks + const sourceDatabases = useDatabases(...sourceDbParams) || []; + const sourceContainers = useDataContainers(...sourceContainerParams) || []; + const targetDatabases = useDatabases(...targetDbParams) || []; + const targetContainers = useDataContainers(...targetContainerParams) || []; - // Custom hooks - const sourceDatabases = useDatabases(...sourceDbParams) || []; - const sourceContainers = useDataContainers(...sourceContainerParams) || []; - const targetDatabases = useDatabases(...targetDbParams) || []; - const targetContainers = useDataContainers(...targetContainerParams) || []; + // Memoize option objects for dropdowns + const sourceDatabaseOptions = React.useMemo( + () => sourceDatabases.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })), + [sourceDatabases], + ); + const sourceContainerOptions = React.useMemo( + () => sourceContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })), + [sourceContainers], + ); + const targetDatabaseOptions = React.useMemo( + () => targetDatabases.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })), + [targetDatabases], + ); + const targetContainerOptions = React.useMemo( + () => targetContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })), + [targetContainers], + ); - // Memoize option objects for dropdowns - const sourceDatabaseOptions = React.useMemo( - () => sourceDatabases.map((db: any) => ({ key: db.name, text: db.name, data: db })), - [sourceDatabases] - ); - const sourceContainerOptions = React.useMemo( - () => sourceContainers.map((c: any) => ({ key: c.name, text: c.name, data: c })), - [sourceContainers] - ); - const targetDatabaseOptions = React.useMemo( - () => targetDatabases.map((db: any) => ({ key: db.name, text: db.name, data: db })), - [targetDatabases] - ); - const targetContainerOptions = React.useMemo( - () => targetContainers.map((c: any) => ({ key: c.name, text: c.name, data: c })), - [targetContainers] - ); + const onDropdownChange = React.useCallback(dropDownChangeHandler(setCopyJobState), [setCopyJobState]); - const onDropdownChange = React.useCallback(dropDownChangeHandler(setCopyJobState), [setCopyJobState]); - - return ( - - {ContainerCopyMessages.selectSourceAndTargetContainersDescription} - - - - ); + return ( + + {ContainerCopyMessages.selectSourceAndTargetContainersDescription} + + + + ); }; - -export default SelectSourceAndTargetContainers; \ No newline at end of file +export default SelectSourceAndTargetContainers; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/memoizedData.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/memoizedData.tsx index 6cb246826..4db64a623 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/memoizedData.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/memoizedData.tsx @@ -3,63 +3,41 @@ import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils"; import { CopyJobContextState, DatabaseParams, DataContainerParams } from "../../../Types"; export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState) { - const { source, target } = copyJobState ?? {}; - const selectedSourceAccount = source?.account; - const selectedTargetAccount = target?.account; - const { - subscriptionId: sourceSubscriptionId, - resourceGroup: sourceResourceGroup, - accountName: sourceAccountName - } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); - const { - subscriptionId: targetSubscriptionId, - resourceGroup: targetResourceGroup, - accountName: targetAccountName - } = getAccountDetailsFromResourceId(selectedTargetAccount?.id); + const { source, target } = copyJobState ?? {}; + const selectedSourceAccount = source?.account; + const selectedTargetAccount = target?.account; + const { + subscriptionId: sourceSubscriptionId, + resourceGroup: sourceResourceGroup, + accountName: sourceAccountName, + } = getAccountDetailsFromResourceId(selectedSourceAccount?.id); + const { + subscriptionId: targetSubscriptionId, + resourceGroup: targetResourceGroup, + accountName: targetAccountName, + } = getAccountDetailsFromResourceId(selectedTargetAccount?.id); - const sourceDbParams = React.useMemo( - () => - [ - sourceSubscriptionId, - sourceResourceGroup, - sourceAccountName, - 'SQL', - ] as DatabaseParams, - [sourceSubscriptionId, sourceResourceGroup, sourceAccountName] - ); + const sourceDbParams = React.useMemo( + () => [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, "SQL"] as DatabaseParams, + [sourceSubscriptionId, sourceResourceGroup, sourceAccountName], + ); - const sourceContainerParams = React.useMemo( - () => - [ - sourceSubscriptionId, - sourceResourceGroup, - sourceAccountName, - source?.databaseId, - 'SQL', - ] as DataContainerParams, - [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, source?.databaseId] - ); + const sourceContainerParams = React.useMemo( + () => + [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, source?.databaseId, "SQL"] as DataContainerParams, + [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, source?.databaseId], + ); - const targetDbParams = React.useMemo( - () => [ - targetSubscriptionId, - targetResourceGroup, - targetAccountName, - 'SQL', - ] as DatabaseParams, - [targetSubscriptionId, targetResourceGroup, targetAccountName] - ); + const targetDbParams = React.useMemo( + () => [targetSubscriptionId, targetResourceGroup, targetAccountName, "SQL"] as DatabaseParams, + [targetSubscriptionId, targetResourceGroup, targetAccountName], + ); - const targetContainerParams = React.useMemo( - () => [ - targetSubscriptionId, - targetResourceGroup, - targetAccountName, - target?.databaseId, - 'SQL', - ] as DataContainerParams, - [targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId] - ); + const targetContainerParams = React.useMemo( + () => + [targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId, "SQL"] as DataContainerParams, + [targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId], + ); - return { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams }; + return { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams }; } diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts index 9332c3e51..8ed16d1c3 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobNavigation.ts @@ -6,90 +6,84 @@ import { useCopyJobPrerequisitesCache } from "./useCopyJobPrerequisitesCache"; import { SCREEN_KEYS, useCreateCopyJobScreensList } from "./useCreateCopyJobScreensList"; type NavigationState = { - screenHistory: string[]; + screenHistory: string[]; }; -type Action = - | { type: "NEXT"; nextScreen: string } - | { type: "PREVIOUS" } - | { type: "RESET" }; +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; - } + 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 { copyJobState, resetCopyJobState } = useCopyJobContext(); - const screens = useCreateCopyJobScreensList(); - const { validationCache: cache } = useCopyJobPrerequisitesCache(); - const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] }); + 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 currentScreenKey = state.screenHistory[state.screenHistory.length - 1]; + const currentScreen = screens.find((screen) => screen.key === currentScreenKey); - const isPrimaryDisabled = useMemo( - () => { - const context = currentScreenKey === SCREEN_KEYS.AssignPermissions ? cache : copyJobState; - return !currentScreen?.validations.every((v) => v.validate(context)); - }, - [currentScreen.key, copyJobState, cache] - ); - const primaryBtnText = useMemo(() => { - if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) { - return "Copy"; - } - return "Next"; - }, [currentScreenKey]); + const isPrimaryDisabled = useMemo(() => { + const context = currentScreenKey === SCREEN_KEYS.AssignPermissions ? cache : copyJobState; + return !currentScreen?.validations.every((v) => v.validate(context)); + }, [currentScreen.key, copyJobState, cache]); + const primaryBtnText = useMemo(() => { + if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) { + return "Copy"; + } + return "Next"; + }, [currentScreenKey]); - const isPreviousDisabled = state.screenHistory.length <= 1; + const isPreviousDisabled = state.screenHistory.length <= 1; - const handleCancel = useCallback(() => { - dispatch({ type: "RESET" }); - resetCopyJobState(); - useSidePanel.getState().closeSidePanel(); - }, []); + const handleCancel = useCallback(() => { + dispatch({ type: "RESET" }); + resetCopyJobState(); + useSidePanel.getState().closeSidePanel(); + }, []); - const handlePrimary = useCallback(() => { - const transitions = { - [SCREEN_KEYS.SelectAccount]: 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) { - submitCreateCopyJob(copyJobState, handleCancel); - } - }, [currentScreenKey, copyJobState]); - - const handlePrevious = useCallback(() => { - dispatch({ type: "PREVIOUS" }); - }, []); - - return { - currentScreen, - isPrimaryDisabled, - isPreviousDisabled, - handlePrimary, - handlePrevious, - handleCancel, - primaryBtnText, + const handlePrimary = useCallback(() => { + const transitions = { + [SCREEN_KEYS.SelectAccount]: 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) { + submitCreateCopyJob(copyJobState, handleCancel); + } + }, [currentScreenKey, copyJobState]); + + const handlePrevious = useCallback(() => { + dispatch({ type: "PREVIOUS" }); + }, []); + + return { + currentScreen, + isPrimaryDisabled, + isPreviousDisabled, + handlePrimary, + handlePrevious, + handleCancel, + primaryBtnText, + }; } diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobPrerequisitesCache.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobPrerequisitesCache.tsx index 471365cb2..457abcbe1 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobPrerequisitesCache.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCopyJobPrerequisitesCache.tsx @@ -1,11 +1,11 @@ import create from "zustand"; interface CopyJobPrerequisitesCacheState { - validationCache: Map; - setValidationCache: (cache: Map) => void; + validationCache: Map; + setValidationCache: (cache: Map) => void; } export const useCopyJobPrerequisitesCache = create((set) => ({ - validationCache: new Map(), - setValidationCache: (cache) => set({ validationCache: cache }) -})); \ No newline at end of file + validationCache: new Map(), + setValidationCache: (cache) => set({ validationCache: cache }), +})); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx index 96fcb3b41..21eab7810 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx @@ -6,82 +6,82 @@ import SelectAccount from "../Screens/SelectAccount"; import SelectSourceAndTargetContainers from "../Screens/SelectSourceAndTargetContainers"; const SCREEN_KEYS = { - SelectAccount: "SelectAccount", - SelectSourceAndTargetContainers: "SelectSourceAndTargetContainers", - PreviewCopyJob: "PreviewCopyJob", - AssignPermissions: "AssignPermissions", + SelectAccount: "SelectAccount", + SelectSourceAndTargetContainers: "SelectSourceAndTargetContainers", + PreviewCopyJob: "PreviewCopyJob", + AssignPermissions: "AssignPermissions", }; type Validation = { - validate: (state: CopyJobContextState | Map) => boolean; - message: string; + validate: (state: CopyJobContextState | Map) => boolean; + message: string; }; type Screen = { - key: string; - component: React.ReactElement; - validations: Validation[]; + key: string; + component: React.ReactElement; + validations: Validation[]; }; function useCreateCopyJobScreensList() { - return React.useMemo( - () => [ - { - key: SCREEN_KEYS.SelectAccount, - component: , - validations: [ - { - validate: (state: CopyJobContextState) => !!state?.source?.subscription && !!state?.source?.account, - message: "Please select a subscription and account to proceed", - }, - ], - }, - { - key: SCREEN_KEYS.SelectSourceAndTargetContainers, - component: , - validations: [ - { - validate: (state: CopyJobContextState) => ( - !!state?.source?.databaseId && !!state?.source?.containerId && !!state?.target?.databaseId && !!state?.target?.containerId - ), - message: "Please select source and target containers to proceed", - }, - ], - }, - { - key: SCREEN_KEYS.PreviewCopyJob, - component: , - validations: [ - { - validate: (state: CopyJobContextState) => !!( - typeof state?.jobName === "string" - && state?.jobName - && /^[a-zA-Z0-9-.]+$/.test(state?.jobName) - ), - message: "Please enter a job name to proceed", - }, - ], - }, - { - key: SCREEN_KEYS.AssignPermissions, - component: , - validations: [ - { - validate: (cache: Map) => { - const cacheValuesIterator = Array.from(cache.values()); - if (cacheValuesIterator.length === 0) return false; - - const allValid = cacheValuesIterator.every((isValid: boolean) => isValid); - return allValid; - }, - message: "Please ensure all previous steps are valid to proceed", - } - ], - }, + return React.useMemo( + () => [ + { + key: SCREEN_KEYS.SelectAccount, + component: , + validations: [ + { + validate: (state: CopyJobContextState) => !!state?.source?.subscription && !!state?.source?.account, + message: "Please select a subscription and account to proceed", + }, ], - [] - ); + }, + { + key: SCREEN_KEYS.SelectSourceAndTargetContainers, + component: , + validations: [ + { + validate: (state: CopyJobContextState) => + !!state?.source?.databaseId && + !!state?.source?.containerId && + !!state?.target?.databaseId && + !!state?.target?.containerId, + message: "Please select source and target containers to proceed", + }, + ], + }, + { + key: SCREEN_KEYS.PreviewCopyJob, + component: , + validations: [ + { + validate: (state: CopyJobContextState) => + !!(typeof state?.jobName === "string" && state?.jobName && /^[a-zA-Z0-9-.]+$/.test(state?.jobName)), + message: "Please enter a job name to proceed", + }, + ], + }, + { + key: SCREEN_KEYS.AssignPermissions, + component: , + validations: [ + { + validate: (cache: Map) => { + const cacheValuesIterator = Array.from(cache.values()); + if (cacheValuesIterator.length === 0) { + return false; + } + + const allValid = cacheValuesIterator.every((isValid: boolean) => isValid); + return allValid; + }, + message: "Please ensure all previous steps are valid to proceed", + }, + ], + }, + ], + [], + ); } export { SCREEN_KEYS, useCreateCopyJobScreensList }; - diff --git a/src/Explorer/ContainerCopy/Enums/index.ts b/src/Explorer/ContainerCopy/Enums/index.ts index c3f82a1e5..c46184b21 100644 --- a/src/Explorer/ContainerCopy/Enums/index.ts +++ b/src/Explorer/ContainerCopy/Enums/index.ts @@ -1,40 +1,40 @@ export enum CopyJobMigrationType { - Offline = "offline", - Online = "online", + Offline = "offline", + Online = "online", } -// all checks will happen +// all checks will happen export enum IdentityType { - SystemAssigned = "systemassigned", // "SystemAssigned" - UserAssigned = "userassigned", // "UserAssigned" - None = "none", // "None" + SystemAssigned = "systemassigned", // "SystemAssigned" + UserAssigned = "userassigned", // "UserAssigned" + None = "none", // "None" } export enum DefaultIdentityType { - SystemAssignedIdentity = "systemassignedidentity", // "SystemAssignedIdentity" + SystemAssignedIdentity = "systemassignedidentity", // "SystemAssignedIdentity" } export enum BackupPolicyType { - Continuous = "Continuous", - Periodic = "Periodic", + Continuous = "Continuous", + Periodic = "Periodic", } export enum CopyJobStatusType { - Pending = "Pending", - InProgress = "InProgress", - Running = "Running", - Partitioning = "Partitioning", - Paused = "Paused", - Skipped = "Skipped", - Completed = "Completed", - Cancelled = "Cancelled", - Failed = "Failed", - Faulted = "Faulted", + Pending = "Pending", + InProgress = "InProgress", + Running = "Running", + Partitioning = "Partitioning", + Paused = "Paused", + Skipped = "Skipped", + Completed = "Completed", + Cancelled = "Cancelled", + Failed = "Failed", + Faulted = "Faulted", } export enum CopyJobActions { - pause = "pause", - resume = "resume", - cancel = "cancel", - complete = "complete", -} \ No newline at end of file + pause = "pause", + resume = "resume", + cancel = "cancel", + complete = "complete", +} diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx index 98fe377e9..80302731e 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx @@ -5,79 +5,72 @@ import { CopyJobActions, CopyJobMigrationType, CopyJobStatusType } from "../../E import { CopyJobType } from "../../Types"; interface CopyJobActionMenuProps { - job: CopyJobType; - handleClick: (job: CopyJobType, action: string) => void; + job: CopyJobType; + handleClick: (job: CopyJobType, action: string) => void; } const CopyJobActionMenu: React.FC = ({ job, handleClick }) => { - if ([ - CopyJobStatusType.Completed, - CopyJobStatusType.Cancelled - ].includes(job.Status)) return null; + if ([CopyJobStatusType.Completed, CopyJobStatusType.Cancelled].includes(job.Status)) { + return null; + } - const getMenuItems = (): IContextualMenuProps["items"] => { - const baseItems = [ - { - key: CopyJobActions.pause, - text: ContainerCopyMessages.MonitorJobs.Actions.pause, - onClick: () => handleClick(job, CopyJobActions.pause) - }, - { - key: CopyJobActions.cancel, - text: ContainerCopyMessages.MonitorJobs.Actions.cancel, - onClick: () => handleClick(job, CopyJobActions.cancel) - }, - { - key: CopyJobActions.resume, - text: ContainerCopyMessages.MonitorJobs.Actions.resume, - onClick: () => handleClick(job, CopyJobActions.resume) - } - ]; + const getMenuItems = (): IContextualMenuProps["items"] => { + const baseItems = [ + { + key: CopyJobActions.pause, + text: ContainerCopyMessages.MonitorJobs.Actions.pause, + onClick: () => handleClick(job, CopyJobActions.pause), + }, + { + key: CopyJobActions.cancel, + text: ContainerCopyMessages.MonitorJobs.Actions.cancel, + onClick: () => handleClick(job, CopyJobActions.cancel), + }, + { + key: CopyJobActions.resume, + text: ContainerCopyMessages.MonitorJobs.Actions.resume, + onClick: () => handleClick(job, CopyJobActions.resume), + }, + ]; - if (CopyJobStatusType.Paused === job.Status) { - return baseItems.filter(item => item.key !== CopyJobActions.pause); - } + if (CopyJobStatusType.Paused === job.Status) { + return baseItems.filter((item) => item.key !== CopyJobActions.pause); + } - if (CopyJobStatusType.Pending === job.Status) { - return baseItems.filter(item => item.key !== CopyJobActions.resume); - } + if (CopyJobStatusType.Pending === job.Status) { + return baseItems.filter((item) => item.key !== CopyJobActions.resume); + } - if ([ - CopyJobStatusType.InProgress, - CopyJobStatusType.Running, - CopyJobStatusType.Partitioning - ].includes(job.Status)) { - const filteredItems = baseItems.filter(item => item.key !== CopyJobActions.resume); - if (job.Mode === CopyJobMigrationType.Online) { - filteredItems.push({ - key: CopyJobActions.complete, - text: ContainerCopyMessages.MonitorJobs.Actions.complete, - onClick: () => handleClick(job, CopyJobActions.complete) - }); - } - return filteredItems; - } + if ( + [CopyJobStatusType.InProgress, CopyJobStatusType.Running, CopyJobStatusType.Partitioning].includes(job.Status) + ) { + const filteredItems = baseItems.filter((item) => item.key !== CopyJobActions.resume); + if (job.Mode === CopyJobMigrationType.Online) { + filteredItems.push({ + key: CopyJobActions.complete, + text: ContainerCopyMessages.MonitorJobs.Actions.complete, + onClick: () => handleClick(job, CopyJobActions.complete), + }); + } + return filteredItems; + } - if ([ - CopyJobStatusType.Failed, - CopyJobStatusType.Faulted, - CopyJobStatusType.Skipped, - ].includes(job.Status)) { - return baseItems.filter(item => item.key === CopyJobActions.resume); - } + if ([CopyJobStatusType.Failed, CopyJobStatusType.Faulted, CopyJobStatusType.Skipped].includes(job.Status)) { + return baseItems.filter((item) => item.key === CopyJobActions.resume); + } - return baseItems; - }; + return baseItems; + }; - return ( - - ); + return ( + + ); }; -export default CopyJobActionMenu; \ No newline at end of file +export default CopyJobActionMenu; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobColumns.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobColumns.tsx index d9473fadf..0ab1c76d4 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobColumns.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobColumns.tsx @@ -6,73 +6,73 @@ import CopyJobActionMenu from "./CopyJobActionMenu"; import CopyJobStatusWithIcon from "./CopyJobStatusWithIcon"; export const getColumns = ( - handleSort: (columnKey: string) => void, - handleActionClick: (job: CopyJobType, action: string) => void, - sortedColumnKey: string | undefined, - isSortedDescending: boolean + handleSort: (columnKey: string) => void, + handleActionClick: (job: CopyJobType, action: string) => void, + sortedColumnKey: string | undefined, + isSortedDescending: boolean, ): IColumn[] => [ - { - key: "LastUpdatedTime", - name: ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime, - fieldName: "LastUpdatedTime", - minWidth: 100, - maxWidth: 150, - isResizable: true, - isSorted: sortedColumnKey === "timestamp", - isSortedDescending: isSortedDescending, - onColumnClick: () => handleSort("timestamp"), - }, - { - key: "Name", - name: ContainerCopyMessages.MonitorJobs.Columns.name, - fieldName: "Name", - minWidth: 90, - maxWidth: 130, - isResizable: true, - isSorted: sortedColumnKey === "Name", - isSortedDescending: isSortedDescending, - onColumnClick: () => handleSort("Name"), - }, - { - key: "Mode", - name: ContainerCopyMessages.MonitorJobs.Columns.mode, - fieldName: "Mode", - minWidth: 70, - maxWidth: 90, - isResizable: true, - isSorted: sortedColumnKey === "Mode", - isSortedDescending: isSortedDescending, - onColumnClick: () => handleSort("Mode"), - }, - { - key: "CompletionPercentage", - name: ContainerCopyMessages.MonitorJobs.Columns.completionPercentage, - fieldName: "CompletionPercentage", - minWidth: 120, - maxWidth: 130, - isResizable: true, - isSorted: sortedColumnKey === "CompletionPercentage", - isSortedDescending: isSortedDescending, - onRender: (job: CopyJobType) => `${job.CompletionPercentage}%`, - onColumnClick: () => handleSort("CompletionPercentage"), - }, - { - key: "CopyJobStatus", - name: ContainerCopyMessages.MonitorJobs.Columns.status, - fieldName: "Status", - minWidth: 80, - maxWidth: 100, - isResizable: true, - isSorted: sortedColumnKey === "Status", - isSortedDescending: isSortedDescending, - onRender: (job: CopyJobType) => , - onColumnClick: () => handleSort("Status"), - }, - { - key: "Actions", - name: ContainerCopyMessages.MonitorJobs.Columns.actions, - minWidth: 200, - isResizable: true, - onRender: (job: CopyJobType) => , - }, - ]; \ No newline at end of file + { + key: "LastUpdatedTime", + name: ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime, + fieldName: "LastUpdatedTime", + minWidth: 100, + maxWidth: 150, + isResizable: true, + isSorted: sortedColumnKey === "timestamp", + isSortedDescending: isSortedDescending, + onColumnClick: () => handleSort("timestamp"), + }, + { + key: "Name", + name: ContainerCopyMessages.MonitorJobs.Columns.name, + fieldName: "Name", + minWidth: 90, + maxWidth: 130, + isResizable: true, + isSorted: sortedColumnKey === "Name", + isSortedDescending: isSortedDescending, + onColumnClick: () => handleSort("Name"), + }, + { + key: "Mode", + name: ContainerCopyMessages.MonitorJobs.Columns.mode, + fieldName: "Mode", + minWidth: 70, + maxWidth: 90, + isResizable: true, + isSorted: sortedColumnKey === "Mode", + isSortedDescending: isSortedDescending, + onColumnClick: () => handleSort("Mode"), + }, + { + key: "CompletionPercentage", + name: ContainerCopyMessages.MonitorJobs.Columns.completionPercentage, + fieldName: "CompletionPercentage", + minWidth: 120, + maxWidth: 130, + isResizable: true, + isSorted: sortedColumnKey === "CompletionPercentage", + isSortedDescending: isSortedDescending, + onRender: (job: CopyJobType) => `${job.CompletionPercentage}%`, + onColumnClick: () => handleSort("CompletionPercentage"), + }, + { + key: "CopyJobStatus", + name: ContainerCopyMessages.MonitorJobs.Columns.status, + fieldName: "Status", + minWidth: 80, + maxWidth: 100, + isResizable: true, + isSorted: sortedColumnKey === "Status", + isSortedDescending: isSortedDescending, + onRender: (job: CopyJobType) => , + onColumnClick: () => handleSort("Status"), + }, + { + key: "Actions", + name: ContainerCopyMessages.MonitorJobs.Columns.actions, + minWidth: 200, + isResizable: true, + onRender: (job: CopyJobType) => , + }, +]; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobStatusWithIcon.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobStatusWithIcon.tsx index 1a62f8d2f..fadcc7186 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobStatusWithIcon.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobStatusWithIcon.tsx @@ -5,46 +5,49 @@ import { CopyJobStatusType } from "../../Enums"; // Styles const iconClass = mergeStyles({ - fontSize: '1em', - marginRight: '0.3em', + fontSize: "1em", + marginRight: "0.3em", }); const classNames = mergeStyleSets({ - [CopyJobStatusType.Pending]: [{ color: '#fe7f2d' }, iconClass], - [CopyJobStatusType.InProgress]: [{ color: '#ee9b00' }, iconClass], - [CopyJobStatusType.Running]: [{ color: '#ee9b00' }, iconClass], - [CopyJobStatusType.Partitioning]: [{ color: '#ee9b00' }, iconClass], - [CopyJobStatusType.Paused]: [{ color: '#bb3e03' }, iconClass], - [CopyJobStatusType.Skipped]: [{ color: '#00bbf9' }, iconClass], - [CopyJobStatusType.Cancelled]: [{ color: '#00bbf9' }, iconClass], - [CopyJobStatusType.Failed]: [{ color: '#d90429' }, iconClass], - [CopyJobStatusType.Faulted]: [{ color: '#d90429' }, iconClass], - [CopyJobStatusType.Completed]: [{ color: '#386641' }, iconClass], - unknown: [{ color: '#000814' }, iconClass], + [CopyJobStatusType.Pending]: [{ color: "#fe7f2d" }, iconClass], + [CopyJobStatusType.InProgress]: [{ color: "#ee9b00" }, iconClass], + [CopyJobStatusType.Running]: [{ color: "#ee9b00" }, iconClass], + [CopyJobStatusType.Partitioning]: [{ color: "#ee9b00" }, iconClass], + [CopyJobStatusType.Paused]: [{ color: "#bb3e03" }, iconClass], + [CopyJobStatusType.Skipped]: [{ color: "#00bbf9" }, iconClass], + [CopyJobStatusType.Cancelled]: [{ color: "#00bbf9" }, iconClass], + [CopyJobStatusType.Failed]: [{ color: "#d90429" }, iconClass], + [CopyJobStatusType.Faulted]: [{ color: "#d90429" }, iconClass], + [CopyJobStatusType.Completed]: [{ color: "#386641" }, iconClass], + unknown: [{ color: "#000814" }, iconClass], }); // Icon Mapping const iconMap: Record = { - [CopyJobStatusType.Pending]: "MSNVideosSolid", - [CopyJobStatusType.InProgress]: "SyncStatusSolid", - [CopyJobStatusType.Running]: "SyncStatusSolid", - [CopyJobStatusType.Partitioning]: "SyncStatusSolid", - [CopyJobStatusType.Paused]: "CirclePauseSolid", - [CopyJobStatusType.Skipped]: "Blocked2Solid", - [CopyJobStatusType.Cancelled]: "Blocked2Solid", - [CopyJobStatusType.Failed]: "AlertSolid", - [CopyJobStatusType.Faulted]: "AlertSolid", - [CopyJobStatusType.Completed]: "CompletedSolid" + [CopyJobStatusType.Pending]: "MSNVideosSolid", + [CopyJobStatusType.InProgress]: "SyncStatusSolid", + [CopyJobStatusType.Running]: "SyncStatusSolid", + [CopyJobStatusType.Partitioning]: "SyncStatusSolid", + [CopyJobStatusType.Paused]: "CirclePauseSolid", + [CopyJobStatusType.Skipped]: "Blocked2Solid", + [CopyJobStatusType.Cancelled]: "Blocked2Solid", + [CopyJobStatusType.Failed]: "AlertSolid", + [CopyJobStatusType.Faulted]: "AlertSolid", + [CopyJobStatusType.Completed]: "CompletedSolid", }; -const CopyJobStatusWithIcon: React.FC<{ status: CopyJobStatusType }> = ({ status }) => ( +const CopyJobStatusWithIcon: React.FC<{ status: CopyJobStatusType }> = ({ status }) => { + const statusText = ContainerCopyMessages.MonitorJobs.Status[status] || "Unknown"; + return ( - - {(ContainerCopyMessages.MonitorJobs.Status as any)[status]} + + {statusText} -); + ); +}; -export default CopyJobStatusWithIcon; \ No newline at end of file +export default CopyJobStatusWithIcon; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobs.NotFound.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobs.NotFound.tsx index e1da73a23..d7f32d3e8 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobs.NotFound.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobs.NotFound.tsx @@ -1,23 +1,22 @@ -import { ActionButton, Image } from '@fluentui/react'; -import React, { useCallback } from 'react'; +import { ActionButton, Image } from "@fluentui/react"; +import React, { useCallback } from "react"; import CopyJobIcon from "../../../../../images/ContainerCopy/copy-jobs.svg"; import * as Actions from "../../Actions/CopyJobActions"; -import ContainerCopyMessages from '../../ContainerCopyMessages'; +import ContainerCopyMessages from "../../ContainerCopyMessages"; -interface CopyJobsNotFoundProps { } +interface CopyJobsNotFoundProps {} const CopyJobsNotFound: React.FC = () => { - - const handleCreateCopyJob = useCallback(Actions.openCreateCopyJobPanel, []); - return ( -
- {ContainerCopyMessages.noCopyJobsTitle} -

{ContainerCopyMessages.noCopyJobsTitle}

- - {ContainerCopyMessages.createCopyJobButtonText} - -
- ); -} + const handleCreateCopyJob = useCallback(Actions.openCreateCopyJobPanel, []); + return ( +
+ {ContainerCopyMessages.noCopyJobsTitle} +

{ContainerCopyMessages.noCopyJobsTitle}

+ + {ContainerCopyMessages.createCopyJobButtonText} + +
+ ); +}; export default CopyJobsNotFound; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobsList.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobsList.tsx index 3cd627c32..17b2be8a4 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobsList.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobsList.tsx @@ -1,102 +1,103 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { - ConstrainMode, - DetailsListLayoutMode, - DetailsRow, - IColumn, - ScrollablePane, - ScrollbarVisibility, - ShimmeredDetailsList, - Stack, - Sticky, - StickyPositionType + ConstrainMode, + DetailsListLayoutMode, + DetailsRow, + IColumn, + ScrollablePane, + ScrollbarVisibility, + ShimmeredDetailsList, + Stack, + Sticky, + StickyPositionType, } from "@fluentui/react"; import React, { useEffect } from "react"; import { CopyJobType } from "../../Types"; import { getColumns } from "./CopyJobColumns"; interface CopyJobsListProps { - jobs: CopyJobType[]; - handleActionClick: (job: CopyJobType, action: string) => void, - pageSize?: number + jobs: CopyJobType[]; + handleActionClick: (job: CopyJobType, action: string) => void; + pageSize?: number; } const styles = { - container: { height: 'calc(100vh - 15em)' } as React.CSSProperties, - stackItem: { position: "relative", marginBottom: "20px" } as React.CSSProperties, + container: { height: "calc(100vh - 15em)" } as React.CSSProperties, + stackItem: { position: "relative", marginBottom: "20px" } as React.CSSProperties, }; const PAGE_SIZE = 100; // Number of items per page const CopyJobsList: React.FC = ({ jobs, handleActionClick, pageSize = PAGE_SIZE }) => { - const [startIndex, setStartIndex] = React.useState(0); - const [sortedJobs, setSortedJobs] = React.useState(jobs); - const [sortedColumnKey, setSortedColumnKey] = React.useState(undefined); - const [isSortedDescending, setIsSortedDescending] = React.useState(false); + const [startIndex] = React.useState(0); + const [sortedJobs, setSortedJobs] = React.useState(jobs); + const [sortedColumnKey, setSortedColumnKey] = React.useState(undefined); + const [isSortedDescending, setIsSortedDescending] = React.useState(false); - useEffect(() => { - setSortedJobs(jobs); - }, [jobs]); + useEffect(() => { + setSortedJobs(jobs); + }, [jobs]); - const handleSort = (columnKey: string) => { - const isDescending = sortedColumnKey === columnKey ? !isSortedDescending : false; - const sorted = [...sortedJobs].sort((current: any, next: any) => { - if (current[columnKey] < next[columnKey]) return isDescending ? 1 : -1; - if (current[columnKey] > next[columnKey]) return isDescending ? -1 : 1; - return 0; - }); - setSortedJobs(sorted); - setSortedColumnKey(columnKey); - setIsSortedDescending(isDescending); - } + const handleSort = (columnKey: string) => { + const isDescending = sortedColumnKey === columnKey ? !isSortedDescending : false; + const sorted = [...sortedJobs].sort((current: any, next: any) => { + if (current[columnKey] < next[columnKey]) { + return isDescending ? 1 : -1; + } + if (current[columnKey] > next[columnKey]) { + return isDescending ? -1 : 1; + } + return 0; + }); + setSortedJobs(sorted); + setSortedColumnKey(columnKey); + setIsSortedDescending(isDescending); + }; - const columns: IColumn[] = React.useMemo( - () => getColumns(handleSort, handleActionClick, sortedColumnKey, isSortedDescending), - [handleSort, handleActionClick, sortedColumnKey, isSortedDescending] - ); + const columns: IColumn[] = React.useMemo( + () => getColumns(handleSort, handleActionClick, sortedColumnKey, isSortedDescending), + [handleSort, handleActionClick, sortedColumnKey, isSortedDescending], + ); - const _handleRowClick = React.useCallback((job: CopyJobType) => { - console.log("Row clicked:", job); - }, []); - - const _onRenderRow = React.useCallback((props: any) => { - return ( -
- -
- ); - }, []); - - // const totalCount = jobs.length; + const _handleRowClick = React.useCallback((job: CopyJobType) => { + // eslint-disable-next-line no-console + console.log("Row clicked:", job); + }, []); + const _onRenderRow = React.useCallback((props: any) => { return ( -
- - - - ( - - {defaultRender({ ...props })} - - )} - /> - - - -
+
+ +
); -} + }, []); -export default CopyJobsList; \ No newline at end of file + // const totalCount = jobs.length; + + return ( +
+ + + + ( + + {defaultRender({ ...props })} + + )} + /> + + + +
+ ); +}; + +export default CopyJobsList; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx index 1227dfd1a..14fbc4ae3 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState.tsx @@ -2,11 +2,11 @@ import create from "zustand"; import { MonitorCopyJobsRef } from "./MonitorCopyJobs"; type MonitorCopyJobsRefStateType = { - ref: MonitorCopyJobsRef; - setRef: (ref: MonitorCopyJobsRef) => void; + ref: MonitorCopyJobsRef; + setRef: (ref: MonitorCopyJobsRef) => void; }; export const MonitorCopyJobsRefState = create((set) => ({ - ref: null, - setRef: (ref) => set({ ref: ref }), -})); \ No newline at end of file + ref: null, + setRef: (ref) => set({ ref: ref }), +})); diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx index 633030325..728cd5bae 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx @@ -1,115 +1,118 @@ -import { MessageBar, MessageBarType, Stack } from '@fluentui/react'; -import ShimmerTree, { IndentLevel } from 'Common/ShimmerTree'; -import React, { forwardRef, useEffect, useImperativeHandle } from 'react'; -import { getCopyJobs, updateCopyJobStatus } from '../Actions/CopyJobActions'; -import { convertToCamelCase } from '../CopyJobUtils'; -import { CopyJobStatusType } from '../Enums'; -import CopyJobsNotFound from '../MonitorCopyJobs/Components/CopyJobs.NotFound'; -import { CopyJobType } from '../Types'; -import CopyJobsList from './Components/CopyJobsList'; +/* eslint-disable react/display-name */ +import { MessageBar, MessageBarType, Stack } from "@fluentui/react"; +import ShimmerTree, { IndentLevel } from "Common/ShimmerTree"; +import React, { forwardRef, useEffect, useImperativeHandle } from "react"; +import { getCopyJobs, updateCopyJobStatus } from "../Actions/CopyJobActions"; +import { convertToCamelCase } from "../CopyJobUtils"; +import { CopyJobStatusType } from "../Enums"; +import CopyJobsNotFound from "../MonitorCopyJobs/Components/CopyJobs.NotFound"; +import { CopyJobType } from "../Types"; +import CopyJobsList from "./Components/CopyJobsList"; const FETCH_INTERVAL_MS = 30 * 1000; // Interval time in milliseconds (30 seconds) -interface MonitorCopyJobsProps { } +interface MonitorCopyJobsProps {} export interface MonitorCopyJobsRef { - refreshJobList: () => void; + 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([]); - const isUpdatingRef = React.useRef(false); // Use ref to track updating state - const isFirstFetchRef = React.useRef(true); // Use ref to track the first fetch + const [loading, setLoading] = React.useState(true); // Start with loading as true + const [error, setError] = React.useState(null); + const [jobs, setJobs] = React.useState([]); + const isUpdatingRef = React.useRef(false); // Use ref to track updating state + const isFirstFetchRef = React.useRef(true); // Use ref to track the first fetch - const indentLevels = React.useMemo( - () => Array(7).fill({ level: 0, width: "100%" }), - [] - ); + const indentLevels = React.useMemo(() => Array(7).fill({ level: 0, width: "100%" }), []); - const fetchJobs = React.useCallback(async () => { - if (isUpdatingRef.current) return; // Skip if an update is in progress - try { - if (isFirstFetchRef.current) setLoading(true); // Show loading spinner only for the first fetch - setError(null); + const fetchJobs = React.useCallback(async () => { + if (isUpdatingRef.current) { + return; + } // Skip if an update is in progress + try { + if (isFirstFetchRef.current) { + setLoading(true); + } // Show loading spinner only for the first fetch + setError(null); - const response = await getCopyJobs(); - setJobs((prevJobs) => { - // Only update jobs if they are different - const isSame = JSON.stringify(prevJobs) === JSON.stringify(response); - return isSame ? prevJobs : response; - }); - } catch (error) { - setError(error.message || "Failed to load copy jobs. Please try again later."); - } finally { - if (isFirstFetchRef.current) { - setLoading(false); // Hide loading spinner after the first fetch - isFirstFetchRef.current = false; // Mark the first fetch as complete - } - } - }, []); + const response = await getCopyJobs(); + setJobs((prevJobs) => { + // Only update jobs if they are different + const isSame = JSON.stringify(prevJobs) === JSON.stringify(response); + return isSame ? prevJobs : response; + }); + } catch (error) { + setError(error.message || "Failed to load copy jobs. Please try again later."); + } finally { + if (isFirstFetchRef.current) { + setLoading(false); // Hide loading spinner after the first fetch + isFirstFetchRef.current = false; // Mark the first fetch as complete + } + } + }, []); - useEffect(() => { - fetchJobs(); - const intervalId = setInterval(fetchJobs, FETCH_INTERVAL_MS); + useEffect(() => { + fetchJobs(); + const intervalId = setInterval(fetchJobs, FETCH_INTERVAL_MS); - return () => clearInterval(intervalId); - }, [fetchJobs]); + return () => clearInterval(intervalId); + }, [fetchJobs]); - useImperativeHandle(ref, () => ({ - refreshJobList: () => { - if (isUpdatingRef.current) { - setError("Please wait for the current update to complete before refreshing."); - return; - } - 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 - const updatedCopyJob = await updateCopyJobStatus(job, action); - if (updatedCopyJob) { - setJobs((prevJobs) => - prevJobs.map((prevJob) => - prevJob.Name === updatedCopyJob.properties.jobName - ? { - ...prevJob, - Status: convertToCamelCase(updatedCopyJob.properties.status) as CopyJobStatusType - } : prevJob - ) - ); - } - } catch (error) { - setError(error.message || "Failed to update copy job status. Please try again later."); - } finally { - isUpdatingRef.current = false; // Mark as not updating - } - }, []); + const handleActionClick = React.useCallback(async (job: CopyJobType, action: string) => { + try { + isUpdatingRef.current = true; // Mark as updating + const updatedCopyJob = await updateCopyJobStatus(job, action); + if (updatedCopyJob) { + setJobs((prevJobs) => + prevJobs.map((prevJob) => + prevJob.Name === updatedCopyJob.properties.jobName + ? { + ...prevJob, + Status: convertToCamelCase(updatedCopyJob.properties.status) as CopyJobStatusType, + } + : prevJob, + ), + ); + } + } catch (error) { + setError(error.message || "Failed to update copy job status. Please try again later."); + } finally { + isUpdatingRef.current = false; // Mark as not updating + } + }, []); - const memoizedJobsList = React.useMemo(() => { - if (loading) return null; - if (jobs.length > 0) return ; - return ; - }, [jobs, loading, handleActionClick]); + const memoizedJobsList = React.useMemo(() => { + if (loading) { + return null; + } + if (jobs.length > 0) { + return ; + } + return ; + }, [jobs, loading, handleActionClick]); - return ( - - {loading && } - {error && ( - setError(null)} - > - {error} - - )} - {memoizedJobsList} - - ); + return ( + + {loading && } + {error && ( + setError(null)}> + {error} + + )} + {memoizedJobsList} + + ); }); -export default MonitorCopyJobs; \ No newline at end of file +export default MonitorCopyJobs; diff --git a/src/Explorer/ContainerCopy/Types/index.ts b/src/Explorer/ContainerCopy/Types/index.ts index 54fbc2dce..e1cbdae00 100644 --- a/src/Explorer/ContainerCopy/Types/index.ts +++ b/src/Explorer/ContainerCopy/Types/index.ts @@ -5,132 +5,128 @@ import Explorer from "../../Explorer"; import { CopyJobMigrationType, CopyJobStatusType } from "../Enums"; export interface ContainerCopyProps { - container: Explorer; + container: Explorer; } export type CopyJobCommandBarBtnType = { - key: string; - iconSrc: string; - label: string; - ariaLabel: string; - disabled?: boolean; - onClick: () => void; + key: string; + iconSrc: string; + label: string; + ariaLabel: string; + disabled?: boolean; + onClick: () => void; }; export type CopyJobTabForwardRefHandle = { - validate: (state: CopyJobContextState) => boolean; + validate: (state: CopyJobContextState) => boolean; }; export type DropdownOptionType = { - key: string, - text: string, - data: any + key: string; + text: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; }; -export type DatabaseParams = [ - string | undefined, - string | undefined, - string | undefined, - ApiType -]; +export type DatabaseParams = [string | undefined, string | undefined, string | undefined, ApiType]; export type DataContainerParams = [ - string | undefined, - string | undefined, - string | undefined, - string | undefined, - ApiType + string | undefined, + string | undefined, + string | undefined, + string | undefined, + ApiType, ]; export interface DatabaseContainerSectionProps { - heading: string, - databaseOptions: DropdownOptionType[], - selectedDatabase: string, - databaseDisabled?: boolean, - databaseOnChange: (ev: any, option: DropdownOptionType) => void, - containerOptions: DropdownOptionType[], - selectedContainer: string, - containerDisabled?: boolean, - containerOnChange: (ev: any, option: DropdownOptionType) => void + heading: string; + databaseOptions: DropdownOptionType[]; + selectedDatabase: string; + databaseDisabled?: boolean; + databaseOnChange: (ev: React.FormEvent, option: DropdownOptionType) => void; + containerOptions: DropdownOptionType[]; + selectedContainer: string; + containerDisabled?: boolean; + containerOnChange: (ev: React.FormEvent, option: DropdownOptionType) => void; } export interface CopyJobContextState { - jobName: string; - migrationType: CopyJobMigrationType; - sourceReadAccessFromTarget?: boolean; - // source details - source: { - subscription: Subscription; - account: DatabaseAccount; - databaseId: string; - containerId: string; - }, - // target details - target: { - subscriptionId: string; - account: DatabaseAccount; - databaseId: string; - containerId: string; - }, + jobName: string; + migrationType: CopyJobMigrationType; + sourceReadAccessFromTarget?: boolean; + // source details + source: { + subscription: Subscription; + account: DatabaseAccount; + databaseId: string; + containerId: string; + }; + // target details + target: { + subscriptionId: string; + account: DatabaseAccount; + databaseId: string; + containerId: string; + }; } export interface CopyJobFlowType { - currentScreen: string; + currentScreen: string; } export interface CopyJobContextProviderType { - flow: CopyJobFlowType; - setFlow: React.Dispatch>; - copyJobState: CopyJobContextState | null; - setCopyJobState: React.Dispatch>; - resetCopyJobState: () => void; + flow: CopyJobFlowType; + setFlow: React.Dispatch>; + copyJobState: CopyJobContextState | null; + setCopyJobState: React.Dispatch>; + resetCopyJobState: () => void; } export type CopyJobType = { - ID: string; - Mode: string; - Name: string; - Status: CopyJobStatusType; - CompletionPercentage: number; - Duration: string; - LastUpdatedTime: string; - timestamp: number; - Error?: CopyJobErrorType -} + ID: string; + Mode: string; + Name: string; + Status: CopyJobStatusType; + CompletionPercentage: number; + Duration: string; + LastUpdatedTime: string; + timestamp: number; + Error?: CopyJobErrorType; +}; export interface CopyJobErrorType { - message: string; - code: string; + message: string; + code: string; } export interface CopyJobError { - message: string; - navigateToStep?: number; + message: string; + navigateToStep?: number; } export type DataTransferJobType = { - id: string; - type: string; - properties: { - jobName: string; - status: string; - lastUpdatedUtcTime: string; - processedCount: number; - totalCount: number; - mode: string; - duration: string; - source: { - databaseName: string; - collectionName: string; - component: string; - }; - destination: { - databaseName: string; - collectionName: string; - component: string; - }; - error: { - message: string; - code: string; - }; - }; -}; \ No newline at end of file + id: string; + type: string; + properties: { + jobName: string; + status: string; + lastUpdatedUtcTime: string; + processedCount: number; + totalCount: number; + mode: string; + duration: string; + source: { + databaseName: string; + collectionName: string; + component: string; + }; + destination: { + databaseName: string; + collectionName: string; + component: string; + }; + error: { + message: string; + code: string; + }; + }; +}; diff --git a/src/Explorer/ContainerCopy/index.tsx b/src/Explorer/ContainerCopy/index.tsx index e54bf1821..9af636502 100644 --- a/src/Explorer/ContainerCopy/index.tsx +++ b/src/Explorer/ContainerCopy/index.tsx @@ -1,23 +1,23 @@ -import { MonitorCopyJobsRefState } from 'Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState'; -import React, { useEffect } from 'react'; -import CopyJobCommandBar from './CommandBar/CopyJobCommandBar'; -import './containerCopyStyles.less'; -import MonitorCopyJobs, { MonitorCopyJobsRef } from './MonitorCopyJobs/MonitorCopyJobs'; -import { ContainerCopyProps } from './Types'; +import { MonitorCopyJobsRefState } from "Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobRefState"; +import React, { useEffect } from "react"; +import CopyJobCommandBar from "./CommandBar/CopyJobCommandBar"; +import "./containerCopyStyles.less"; +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 ( -
- - -
- ); + const monitorCopyJobsRef = React.useRef(); + useEffect(() => { + if (monitorCopyJobsRef.current) { + MonitorCopyJobsRefState.getState().setRef(monitorCopyJobsRef.current); + } + }, [monitorCopyJobsRef.current]); + return ( +
+ + +
+ ); }; -export default ContainerCopyPanel; \ No newline at end of file +export default ContainerCopyPanel; diff --git a/src/Explorer/Panes/PanelContainerComponent.tsx b/src/Explorer/Panes/PanelContainerComponent.tsx index 871c1dd5d..44235d2b0 100644 --- a/src/Explorer/Panes/PanelContainerComponent.tsx +++ b/src/Explorer/Panes/PanelContainerComponent.tsx @@ -8,7 +8,7 @@ export interface PanelContainerProps { panelContent?: JSX.Element; isConsoleExpanded: boolean; isOpen: boolean; - hasConsole: boolean + hasConsole?: boolean; isConsoleAnimationFinished?: boolean; panelWidth?: string; onRenderNavigationContent?: IRenderFunction; diff --git a/src/Main.tsx b/src/Main.tsx index 2b61c594e..c853c40e4 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -78,24 +78,22 @@ const App: React.FunctionComponent = () => { } StyleConstants.updateStyles(); const explorer = useKnockoutExplorer(config?.platform); - console.log("Using config: ", config); + // console.log("Using config: ", config); if (!explorer) { return ; } - console.log("Using explorer: ", explorer); - console.log("Using userContext: ", userContext); + // console.log("Using explorer: ", explorer); + // console.log("Using userContext: ", userContext); return (
- { - userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? ( - - ) : ( - - ) - } + {userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? ( + + ) : ( + + )} diff --git a/src/Utils/AuthorizationUtils.test.ts b/src/Utils/AuthorizationUtils.test.ts index bc47c7a5f..650f2ed17 100644 --- a/src/Utils/AuthorizationUtils.test.ts +++ b/src/Utils/AuthorizationUtils.test.ts @@ -8,6 +8,7 @@ describe("AuthorizationUtils", () => { const setAadDataPlane = (enabled: boolean) => { updateUserContext({ features: { + enableContainerCopy: false, enableAadDataPlane: enabled, canExceedMaximumValue: false, cosmosdb: false, diff --git a/src/Utils/CopyJobAuthUtils.ts b/src/Utils/CopyJobAuthUtils.ts index f18212741..19e8f5b18 100644 --- a/src/Utils/CopyJobAuthUtils.ts +++ b/src/Utils/CopyJobAuthUtils.ts @@ -1,9 +1,12 @@ import { userContext } from "UserContext"; export function getCopyJobAuthorizationHeader(token: string = ""): Headers { - const headers = new Headers(); - const authToken = token ? `Bearer ${token}` : userContext.authorizationToken; - headers.append("Authorization", authToken); - headers.append("Content-Type", "application/json"); - return headers; -} \ No newline at end of file + if (!token && !userContext.authorizationToken) { + throw new Error("Authorization token is missing"); + } + const headers = new Headers(); + const authToken = token ? `Bearer ${token}` : userContext.authorizationToken ?? ""; + headers.append("Authorization", authToken); + headers.append("Content-Type", "application/json"); + return headers; +} diff --git a/src/Utils/arm/RbacUtils.ts b/src/Utils/arm/RbacUtils.ts index 1ee14007d..aff39e389 100644 --- a/src/Utils/arm/RbacUtils.ts +++ b/src/Utils/arm/RbacUtils.ts @@ -3,117 +3,118 @@ import { armRequest } from "Utils/arm/request"; import { getCopyJobAuthorizationHeader } from "../CopyJobAuthUtils"; export type FetchAccountDetailsParams = { - subscriptionId: string; - resourceGroupName: string; - accountName: string; + subscriptionId: string; + resourceGroupName: string; + accountName: string; }; export type RoleAssignmentPropertiesType = { - roleDefinitionId: string; - principalId: string; - scope: string; + roleDefinitionId: string; + principalId: string; + scope: string; }; export type RoleAssignmentType = { - id: string; - name: string; - properties: RoleAssignmentPropertiesType; - type: string; + id: string; + name: string; + properties: RoleAssignmentPropertiesType; + type: string; }; type RoleDefinitionDataActions = { - dataActions: string[]; + dataActions: string[]; }; export type RoleDefinitionType = { - assignableScopes: string[]; - id: string; - name: string; - permissions: RoleDefinitionDataActions[]; - resourceGroup: string; - roleName: string; - type: string; - typePropertiesType: string; + assignableScopes: string[]; + id: string; + name: string; + permissions: RoleDefinitionDataActions[]; + resourceGroup: string; + roleName: string; + type: string; + typePropertiesType: string; }; const apiVersion = "2025-04-15"; const getArmBaseUrl = (): string => { - const base = configContext.ARM_ENDPOINT; - return base.endsWith("/") ? base.slice(0, -1) : base; + const base = configContext.ARM_ENDPOINT; + return base.endsWith("/") ? base.slice(0, -1) : base; }; -const buildArmUrl = (path: string): string => - `${getArmBaseUrl()}${path}?api-version=${apiVersion}`; +const buildArmUrl = (path: string): string => `${getArmBaseUrl()}${path}?api-version=${apiVersion}`; const handleResponse = async (response: Response, context: string) => { - if (!response.ok) { - const body = await response.text().catch(() => ""); - throw new Error( - `Failed to fetch ${context}. Status: ${response.status}. ${body || ""}` - ); - } - return response.json(); + if (!response.ok) { + const body = await response.text().catch(() => ""); + throw new Error(`Failed to fetch ${context}. Status: ${response.status}. ${body || ""}`); + } + return response.json(); }; export const fetchRoleAssignments = async ( - subscriptionId: string, - resourceGroupName: string, - accountName: string, - principalId: string + subscriptionId: string, + resourceGroupName: string, + accountName: string, + principalId: string, ): Promise => { - const uri = buildArmUrl( - `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlRoleAssignments` - ); + const uri = buildArmUrl( + `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlRoleAssignments`, + ); - const response = await fetch(uri, { method: "GET", headers: getCopyJobAuthorizationHeader() }); - const data = await handleResponse(response, "role assignments"); + const response = await fetch(uri, { method: "GET", headers: getCopyJobAuthorizationHeader() }); + const data = await handleResponse(response, "role assignments"); - return (data.value || []).filter( - (assignment: RoleAssignmentType) => - assignment?.properties?.principalId === principalId - ); + return (data.value || []).filter( + (assignment: RoleAssignmentType) => assignment?.properties?.principalId === principalId, + ); }; -export const fetchRoleDefinitions = async ( - roleAssignments: RoleAssignmentType[] -): Promise => { - const roleDefinitionIds = roleAssignments.map(assignment => assignment.properties.roleDefinitionId); - const uniqueRoleDefinitionIds = Array.from(new Set(roleDefinitionIds)); +export const fetchRoleDefinitions = async (roleAssignments: RoleAssignmentType[]): Promise => { + const roleDefinitionIds = roleAssignments.map((assignment) => assignment.properties.roleDefinitionId); + const uniqueRoleDefinitionIds = Array.from(new Set(roleDefinitionIds)); - const headers = getCopyJobAuthorizationHeader(); - const roleDefinitionUris = uniqueRoleDefinitionIds.map((id) => buildArmUrl(id)); + const headers = getCopyJobAuthorizationHeader(); + const roleDefinitionUris = uniqueRoleDefinitionIds.map((id) => buildArmUrl(id)); - const promises = roleDefinitionUris.map((url) => fetch(url, { method: "GET", headers })); - const responses = await Promise.all(promises); + const promises = roleDefinitionUris.map((url) => fetch(url, { method: "GET", headers })); + const responses = await Promise.all(promises); - const roleDefinitions = await Promise.all( - responses.map((res, i) => handleResponse(res, `role definition ${uniqueRoleDefinitionIds[i]}`)) - ); + const roleDefinitions = await Promise.all( + responses.map((res, i) => handleResponse(res, `role definition ${uniqueRoleDefinitionIds[i]}`)), + ); - return roleDefinitions; + return roleDefinitions; }; export const assignRole = async ( - subscriptionId: string, - resourceGroupName: string, - accountName: string, - principalId: string -): Promise => { - const accountScope = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; - const roleDefinitionId = `${accountScope}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001`; - const roleAssignmentName = crypto.randomUUID(); - const path = `${accountScope}/sqlRoleAssignments/${roleAssignmentName}`; + subscriptionId: string, + resourceGroupName: string, + accountName: string, + principalId: string, +): Promise => { + if (!principalId) { + return null; + } + const accountScope = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; + const roleDefinitionId = `${accountScope}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001`; + const roleAssignmentName = crypto.randomUUID(); + const path = `${accountScope}/sqlRoleAssignments/${roleAssignmentName}`; - const body = { - properties: { - roleDefinitionId, - scope: `${accountScope}/`, - principalId - } - }; - const response: RoleAssignmentType = await armRequest({ - host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body - }); - return response; + const body = { + properties: { + roleDefinitionId, + scope: `${accountScope}/`, + principalId, + }, + }; + const response: RoleAssignmentType = await armRequest({ + host: configContext.ARM_ENDPOINT, + path, + method: "PUT", + apiVersion, + body, + }); + return response; }; diff --git a/src/Utils/arm/databaseAccountUtils.ts b/src/Utils/arm/databaseAccountUtils.ts index be160f5ba..e696c84f5 100644 --- a/src/Utils/arm/databaseAccountUtils.ts +++ b/src/Utils/arm/databaseAccountUtils.ts @@ -4,35 +4,34 @@ import { configContext } from "../../ConfigContext"; const apiVersion = "2025-04-15"; export type FetchAccountDetailsParams = { - subscriptionId: string; - resourceGroupName: string; - accountName: string; + subscriptionId: string; + resourceGroupName: string; + accountName: string; }; const buildUrl = (params: FetchAccountDetailsParams): string => { - const { subscriptionId, resourceGroupName, accountName } = params; + const { subscriptionId, resourceGroupName, accountName } = params; - let armEndpoint = configContext.ARM_ENDPOINT; - if (armEndpoint.endsWith("/")) { - armEndpoint = armEndpoint.slice(0, -1); - } - return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}?api-version=${apiVersion}`; -} - -export async function fetchDatabaseAccount( - subscriptionId: string, - resourceGroupName: string, - accountName: string -) { - const headers = new Headers(); - headers.append("Authorization", userContext.authorizationToken); - headers.append("Content-Type", "application/json"); - const uri = buildUrl({ subscriptionId, resourceGroupName, accountName }); - const response = await fetch(uri, { method: "GET", headers: headers }); - - if (!response.ok) { - throw new Error(`Error fetching database account: ${response.statusText}`); - } - const account: DatabaseAccount = await response.json(); - return account; + let armEndpoint = configContext.ARM_ENDPOINT; + if (armEndpoint.endsWith("/")) { + armEndpoint = armEndpoint.slice(0, -1); + } + return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}?api-version=${apiVersion}`; +}; + +export async function fetchDatabaseAccount(subscriptionId: string, resourceGroupName: string, accountName: string) { + if (!userContext.authorizationToken) { + return Promise.reject("Authorization token is missing"); + } + const headers = new Headers(); + headers.append("Authorization", userContext.authorizationToken); + headers.append("Content-Type", "application/json"); + const uri = buildUrl({ subscriptionId, resourceGroupName, accountName }); + const response = await fetch(uri, { method: "GET", headers: headers }); + + if (!response.ok) { + throw new Error(`Error fetching database account: ${response.statusText}`); + } + const account: DatabaseAccount = await response.json(); + return account; } diff --git a/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts b/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts index 7cf57d0d2..a6168aba1 100644 --- a/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts +++ b/src/Utils/arm/generatedClients/dataTransferService/dataTransferJobs.ts @@ -83,7 +83,7 @@ export async function listByDatabaseAccount( subscriptionId: string, resourceGroupName: string, accountName: string, - signal?: AbortSignal + signal?: AbortSignal, ): Promise { const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`; return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion, signal }); diff --git a/src/Utils/arm/generatedClients/dataTransferService/types.ts b/src/Utils/arm/generatedClients/dataTransferService/types.ts index 44ced9687..8807b6873 100644 --- a/src/Utils/arm/generatedClients/dataTransferService/types.ts +++ b/src/Utils/arm/generatedClients/dataTransferService/types.ts @@ -8,107 +8,106 @@ /* Base class for all DataTransfer source/sink */ export interface DataTransferDataSourceSink { - /* undocumented */ - component: "CosmosDBCassandra" | "CosmosDBMongo" | "CosmosDBMongoVCore" | "CosmosDBSql" | "AzureBlobStorage"; + /* undocumented */ + component: "CosmosDBCassandra" | "CosmosDBMongo" | "CosmosDBMongoVCore" | "CosmosDBSql" | "AzureBlobStorage"; } /* A base CosmosDB data source/sink */ export type BaseCosmosDataTransferDataSourceSink = DataTransferDataSourceSink & { - /* undocumented */ - remoteAccountName?: string; + /* undocumented */ + remoteAccountName?: string; }; /* A CosmosDB Cassandra API data source/sink */ export type CosmosCassandraDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & { - /* undocumented */ - keyspaceName: string; - /* undocumented */ - tableName: string; + /* undocumented */ + keyspaceName: string; + /* undocumented */ + tableName: string; }; /* A CosmosDB Mongo API data source/sink */ export type CosmosMongoDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & { - /* undocumented */ - databaseName: string; - /* undocumented */ - collectionName: string; + /* undocumented */ + databaseName: string; + /* undocumented */ + collectionName: string; }; /* A CosmosDB Mongo vCore API data source/sink */ export type CosmosMongoVCoreDataTransferDataSourceSink = DataTransferDataSourceSink & { - /* undocumented */ - databaseName: string; - /* undocumented */ - collectionName: string; - /* undocumented */ - hostName?: string; - /* undocumented */ - connectionStringKeyVaultUri?: string; + /* undocumented */ + databaseName: string; + /* undocumented */ + collectionName: string; + /* undocumented */ + hostName?: string; + /* undocumented */ + connectionStringKeyVaultUri?: string; }; - /* A CosmosDB No Sql API data source/sink */ export type CosmosSqlDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & { - /* undocumented */ - databaseName: string; - /* undocumented */ - containerName: string; + /* undocumented */ + databaseName: string; + /* undocumented */ + containerName: string; }; /* An Azure Blob Storage data source/sink */ export type AzureBlobDataTransferDataSourceSink = DataTransferDataSourceSink & { - /* undocumented */ - containerName: string; - /* undocumented */ - endpointUrl?: string; + /* undocumented */ + containerName: string; + /* undocumented */ + endpointUrl?: string; }; /* The properties of a DataTransfer Job */ export interface DataTransferJobProperties { - /* Job Name */ - readonly jobName?: string; - /* Source DataStore details */ - source: DataTransferDataSourceSink; + /* Job Name */ + readonly jobName?: string; + /* Source DataStore details */ + source: DataTransferDataSourceSink; - /* Destination DataStore details */ - destination: DataTransferDataSourceSink; + /* Destination DataStore details */ + destination: DataTransferDataSourceSink; - /* Job Status */ - readonly status?: string; - /* Processed Count. */ - readonly processedCount?: number - /* Total Count. */ - readonly totalCount?: number; - /* Last Updated Time (ISO-8601 format). */ - readonly lastUpdatedUtcTime?: string; - /* Worker count */ - workerCount?: number; - /* Error response for Faulted job */ - readonly error?: unknown; + /* Job Status */ + readonly status?: string; + /* Processed Count. */ + readonly processedCount?: number; + /* Total Count. */ + readonly totalCount?: number; + /* Last Updated Time (ISO-8601 format). */ + readonly lastUpdatedUtcTime?: string; + /* Worker count */ + workerCount?: number; + /* Error response for Faulted job */ + readonly error?: unknown; - /* Total Duration of Job */ - readonly duration?: string - /* Mode of job execution */ - mode?: "Offline" | "Online"; + /* Total Duration of Job */ + readonly duration?: string; + /* Mode of job execution */ + mode?: "Offline" | "Online"; } /* Parameters to create Data Transfer Job */ export type CreateJobRequest = unknown & { - /* Data Transfer Create Job Properties */ - properties: DataTransferJobProperties; + /* Data Transfer Create Job Properties */ + properties: DataTransferJobProperties; }; /* A Cosmos DB Data Transfer Job */ export type DataTransferJobGetResults = unknown & { - /* undocumented */ - properties?: DataTransferJobProperties; + /* undocumented */ + properties?: DataTransferJobProperties; }; /* The List operation response, that contains the Data Transfer jobs and their properties. */ export interface DataTransferJobFeedResults { - /* List of Data Transfer jobs and their properties. */ - readonly value?: DataTransferJobGetResults[]; + /* List of Data Transfer jobs and their properties. */ + readonly value?: DataTransferJobGetResults[]; - /* URL to get the next set of Data Transfer job list results if there are any. */ - readonly nextLink?: string; + /* URL to get the next set of Data Transfer job list results if there are any. */ + readonly nextLink?: string; } diff --git a/src/Utils/arm/identityUtils.ts b/src/Utils/arm/identityUtils.ts index be6969f0b..48700ea1f 100644 --- a/src/Utils/arm/identityUtils.ts +++ b/src/Utils/arm/identityUtils.ts @@ -6,51 +6,52 @@ import { armRequest } from "./request"; const apiVersion = "2025-04-15"; const updateIdentity = async ( - subscriptionId: string, - resourceGroupName: string, - accountName: string, - body: object -): Promise => { - const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; - const response: { status: string } = await armRequest({ - host: configContext.ARM_ENDPOINT, path, method: "PATCH", apiVersion, body - }); - if (response.status === "Succeeded") { - const account = await fetchDatabaseAccount(subscriptionId, resourceGroupName, accountName); - return account; - } - return null; + subscriptionId: string, + resourceGroupName: string, + accountName: string, + body: object, +): Promise => { + const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`; + const response: { status: string } = await armRequest({ + host: configContext.ARM_ENDPOINT, + path, + method: "PATCH", + apiVersion, + body, + }); + if (response.status === "Succeeded") { + const account = await fetchDatabaseAccount(subscriptionId, resourceGroupName, accountName); + return account; + } + return null; }; const updateSystemIdentity = async ( - subscriptionId: string, - resourceGroupName: string, - accountName: string -): Promise => { - const body = { - identity: { - type: "SystemAssigned" - } - }; - const updatedAccount = await updateIdentity(subscriptionId, resourceGroupName, accountName, body); - return updatedAccount; + subscriptionId: string, + resourceGroupName: string, + accountName: string, +): Promise => { + const body = { + identity: { + type: "SystemAssigned", + }, + }; + const updatedAccount = await updateIdentity(subscriptionId, resourceGroupName, accountName, body); + return updatedAccount; }; const updateDefaultIdentity = async ( - subscriptionId: string, - resourceGroupName: string, - accountName: string -): Promise => { - const body = { - properties: { - defaultIdentity: "SystemAssignedIdentity" - } - }; - const updatedAccount = await updateIdentity(subscriptionId, resourceGroupName, accountName, body); - return updatedAccount; + subscriptionId: string, + resourceGroupName: string, + accountName: string, +): Promise => { + const body = { + properties: { + defaultIdentity: "SystemAssignedIdentity", + }, + }; + const updatedAccount = await updateIdentity(subscriptionId, resourceGroupName, accountName, body); + return updatedAccount; }; - - export { updateDefaultIdentity, updateSystemIdentity }; - diff --git a/src/Utils/arm/request.ts b/src/Utils/arm/request.ts index c9627ca1a..3471fb67b 100644 --- a/src/Utils/arm/request.ts +++ b/src/Utils/arm/request.ts @@ -83,7 +83,7 @@ export async function armRequestWithoutPolling({ method, headers, body: requestBody ? JSON.stringify(requestBody) : undefined, - signal + signal, }); if (!response.ok) { @@ -119,7 +119,7 @@ export async function armRequest({ queryParams, contentType, customHeaders, - signal + signal, }: Options): Promise { const armRequestResult = await armRequestWithoutPolling({ host, @@ -130,7 +130,7 @@ export async function armRequest({ queryParams, contentType, customHeaders, - signal + signal, }); const operationStatusUrl = armRequestResult.operationStatusUrl; if (operationStatusUrl) { diff --git a/src/hooks/useDataContainers.tsx b/src/hooks/useDataContainers.tsx index d79a94fcc..ecd6e55e1 100644 --- a/src/hooks/useDataContainers.tsx +++ b/src/hooks/useDataContainers.tsx @@ -7,76 +7,69 @@ import { getCopyJobAuthorizationHeader } from "../Utils/CopyJobAuthUtils"; const apiVersion = "2023-09-15"; export interface FetchDataContainersListParams { - subscriptionId: string; - resourceGroupName: string; - databaseName: string; - accountName: string; - apiType?: ApiType; + subscriptionId: string; + resourceGroupName: string; + databaseName: string; + accountName: string; + apiType?: ApiType; } const buildReadDataContainersListUrl = (params: FetchDataContainersListParams): string => { - const { subscriptionId, resourceGroupName, accountName, databaseName, apiType } = params; - const databaseEndpoint = getDatabaseEndpoint(apiType); - const collectionEndpoint = getCollectionEndpoint(apiType); + const { subscriptionId, resourceGroupName, accountName, databaseName, apiType } = params; + const databaseEndpoint = getDatabaseEndpoint(apiType); + const collectionEndpoint = getCollectionEndpoint(apiType); - let armEndpoint = configContext.ARM_ENDPOINT; - if (armEndpoint.endsWith("/")) { - armEndpoint = armEndpoint.slice(0, -1); - } - return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/${databaseEndpoint}/${databaseName}/${collectionEndpoint}?api-version=${apiVersion}`; -} + let armEndpoint = configContext.ARM_ENDPOINT; + if (armEndpoint.endsWith("/")) { + armEndpoint = armEndpoint.slice(0, -1); + } + return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/${databaseEndpoint}/${databaseName}/${collectionEndpoint}?api-version=${apiVersion}`; +}; const fetchDataContainersList = async ( - subscriptionId: string, - resourceGroupName: string, - accountName: string, - databaseName: string, - apiType: ApiType + subscriptionId: string, + resourceGroupName: string, + accountName: string, + databaseName: string, + apiType: ApiType, ): Promise => { - const uri = buildReadDataContainersListUrl({ - subscriptionId, - resourceGroupName, - accountName, - databaseName, - apiType - }); - const headers = getCopyJobAuthorizationHeader(); + const uri = buildReadDataContainersListUrl({ + subscriptionId, + resourceGroupName, + accountName, + databaseName, + apiType, + }); + const headers = getCopyJobAuthorizationHeader(); - const response = await fetch(uri, { - method: "GET", - headers: headers - }); + const response = await fetch(uri, { + method: "GET", + headers: headers, + }); - if (!response.ok) { - throw new Error("Failed to fetch containers"); - } + if (!response.ok) { + throw new Error("Failed to fetch containers"); + } - const data = await response.json(); - return data.value; + const data = await response.json(); + return data.value; }; export function useDataContainers( - subscriptionId: string, - resourceGroupName: string, - accountName: string, - databaseName: string, - apiType: ApiType + subscriptionId: string, + resourceGroupName: string, + accountName: string, + databaseName: string, + apiType: ApiType, ): DatabaseModel[] | undefined { - const { data } = useSWR( - () => ( - subscriptionId && resourceGroupName && accountName && databaseName && apiType ? [ - "fetchContainersLinkedToDatabases", - subscriptionId, resourceGroupName, accountName, databaseName, apiType - ] : undefined - ), - (_, subscriptionId, resourceGroupName, accountName, databaseName, apiType) => fetchDataContainersList( - subscriptionId, - resourceGroupName, - accountName, - databaseName, - apiType - ), - ); + const { data } = useSWR( + () => + subscriptionId && resourceGroupName && accountName && databaseName && apiType + ? ["fetchContainersLinkedToDatabases", subscriptionId, resourceGroupName, accountName, databaseName, apiType] + : undefined, + (_, subscriptionId, resourceGroupName, accountName, databaseName, apiType) => + fetchDataContainersList(subscriptionId, resourceGroupName, accountName, databaseName, apiType), + ); - return data; -} \ No newline at end of file + return data; +} diff --git a/src/hooks/useDatabaseAccounts.tsx b/src/hooks/useDatabaseAccounts.tsx index efa36137a..18474b6fc 100644 --- a/src/hooks/useDatabaseAccounts.tsx +++ b/src/hooks/useDatabaseAccounts.tsx @@ -11,7 +11,10 @@ interface AccountListResult { value: DatabaseAccount[]; } -export async function fetchDatabaseAccounts(subscriptionId: string, accessToken: string = ""): Promise { +export async function fetchDatabaseAccounts( + subscriptionId: string, + accessToken: string = "", +): Promise { if (!accessToken && !userContext.authorizationToken) { return []; } @@ -61,15 +64,15 @@ export async function fetchDatabaseAccountsFromGraph( subscriptions: [subscriptionId], ...(skipToken ? { - options: { - $skipToken: skipToken, - } as QueryRequestOptions, - } + options: { + $skipToken: skipToken, + } as QueryRequestOptions, + } : { - options: { - $top: 150, - } as QueryRequestOptions, - }), + options: { + $top: 150, + } as QueryRequestOptions, + }), }; const response = await fetch(managementResourceGraphAPIURL, { diff --git a/src/hooks/useDatabases.tsx b/src/hooks/useDatabases.tsx index a9f0237c3..c0d77b8e0 100644 --- a/src/hooks/useDatabases.tsx +++ b/src/hooks/useDatabases.tsx @@ -7,60 +7,59 @@ import { getCopyJobAuthorizationHeader } from "../Utils/CopyJobAuthUtils"; const apiVersion = "2023-09-15"; export interface FetchDatabasesListParams { - subscriptionId: string; - resourceGroupName: string; - accountName: string; - apiType?: ApiType; + subscriptionId: string; + resourceGroupName: string; + accountName: string; + apiType?: ApiType; } const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string => { - const { subscriptionId, resourceGroupName, accountName, apiType } = params; - const databaseEndpoint = getDatabaseEndpoint(apiType); + const { subscriptionId, resourceGroupName, accountName, apiType } = params; + const databaseEndpoint = getDatabaseEndpoint(apiType); - let armEndpoint = configContext.ARM_ENDPOINT; - if (armEndpoint.endsWith("/")) { - armEndpoint = armEndpoint.slice(0, -1); - } - return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/${databaseEndpoint}?api-version=${apiVersion}`; -} + let armEndpoint = configContext.ARM_ENDPOINT; + if (armEndpoint.endsWith("/")) { + armEndpoint = armEndpoint.slice(0, -1); + } + return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/${databaseEndpoint}?api-version=${apiVersion}`; +}; -const fetchDatabasesList = async (subscriptionId: string, resourceGroupName: string, accountName: string, apiType: ApiType): Promise => { - const uri = buildReadDatabasesListUrl({ subscriptionId, resourceGroupName, accountName, apiType }); - const headers = getCopyJobAuthorizationHeader(); +const fetchDatabasesList = async ( + subscriptionId: string, + resourceGroupName: string, + accountName: string, + apiType: ApiType, +): Promise => { + const uri = buildReadDatabasesListUrl({ subscriptionId, resourceGroupName, accountName, apiType }); + const headers = getCopyJobAuthorizationHeader(); - const response = await fetch(uri, { - method: "GET", - headers: headers - }); + const response = await fetch(uri, { + method: "GET", + headers: headers, + }); - if (!response.ok) { - throw new Error("Failed to fetch databases"); - } + if (!response.ok) { + throw new Error("Failed to fetch databases"); + } - const data = await response.json(); - return data.value; + const data = await response.json(); + return data.value; }; export function useDatabases( - subscriptionId: string, - resourceGroupName: string, - accountName: string, - apiType: ApiType + subscriptionId: string, + resourceGroupName: string, + accountName: string, + apiType: ApiType, ): DatabaseModel[] | undefined { - const { data } = useSWR( - () => ( - subscriptionId && resourceGroupName && accountName && apiType ? [ - "fetchDatabasesLinkedToResource", - subscriptionId, resourceGroupName, accountName, apiType - ] : undefined - ), - (_, subscriptionId, resourceGroupName, accountName, apiType) => fetchDatabasesList( - subscriptionId, - resourceGroupName, - accountName, - apiType - ), - ); + const { data } = useSWR( + () => + subscriptionId && resourceGroupName && accountName && apiType + ? ["fetchDatabasesLinkedToResource", subscriptionId, resourceGroupName, accountName, apiType] + : undefined, + (_, subscriptionId, resourceGroupName, accountName, apiType) => + fetchDatabasesList(subscriptionId, resourceGroupName, accountName, apiType), + ); - return data; -} \ No newline at end of file + return data; +}