Skip to content

Commit fa18b85

Browse files
copy job process performance enhancement (#2273)
1 parent d060f22 commit fa18b85

File tree

16 files changed

+159
-138
lines changed

16 files changed

+159
-138
lines changed

src/Common/DatabaseAccountUtility.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export const getDatabaseEndpoint = (apiType: ApiType): string => {
4444
return "gremlinDatabases";
4545
case "Tables":
4646
return "tables";
47-
default:
4847
case "SQL":
48+
default:
4949
return "sqlDatabases";
5050
}
5151
};
@@ -58,8 +58,8 @@ export const getCollectionEndpoint = (apiType: ApiType): string => {
5858
return "tables";
5959
case "Gremlin":
6060
return "graphs";
61-
default:
6261
case "SQL":
62+
default:
6363
return "containers";
6464
}
6565
};

src/Contracts/DataModels.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,6 @@ export interface DatabaseAccountSystemData {
3636

3737
export interface DatabaseAccountBackupPolicy {
3838
type: string;
39-
/* periodicModeProperties?: {
40-
backupIntervalInMinutes: number;
41-
backupRetentionIntervalInHours: number;
42-
backupStorageRedundancy: string;
43-
};
44-
continuousModeProperties?: {
45-
tier: string;
46-
}; */
4739
}
4840

4941
export interface DatabaseAccountExtendedProperties {
@@ -73,6 +65,7 @@ export interface DatabaseAccountExtendedProperties {
7365
publicNetworkAccess?: string;
7466
enablePriorityBasedExecution?: boolean;
7567
vcoreMongoEndpoint?: string;
68+
enableAllVersionsAndDeletesChangeFeed?: boolean;
7669
}
7770

7871
export interface DatabaseAccountResponseLocation {

src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
extractErrorMessage,
2424
formatUTCDateTime,
2525
getAccountDetailsFromResourceId,
26+
isIntraAccountCopy,
2627
} from "../CopyJobUtils";
2728
import CreateCopyJobScreensProvider from "../CreateCopyJob/Screens/CreateCopyJobScreensProvider";
2829
import { CopyJobActions, CopyJobStatusType } from "../Enums/CopyJobEnums";
@@ -75,7 +76,6 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
7576
}
7677
copyJobsAbortController = null;
7778

78-
/* added a lower bound to "0" and upper bound to "100" */
7979
const calculateCompletionPercentage = (processed: number, total: number): number => {
8080
if (
8181
typeof processed !== "number" ||
@@ -139,11 +139,12 @@ export const submitCreateCopyJob = async (state: CopyJobContextState, onSuccess:
139139
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(
140140
userContext.databaseAccount?.id || "",
141141
);
142+
const isSameAccount = isIntraAccountCopy(source?.account?.id, target?.account?.id);
142143
const body = {
143144
properties: {
144145
source: {
145146
component: "CosmosDBSql",
146-
remoteAccountName: source?.account?.name,
147+
...(isSameAccount ? {} : { remoteAccountName: source?.account?.name }),
147148
databaseName: source?.databaseId,
148149
containerName: source?.containerId,
149150
},

src/Explorer/ContainerCopy/ContainerCopyMessages.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ export default {
5555
"To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.",
5656
intraAccountOnlineDescription: (accountName: string) =>
5757
`Follow the steps below to enable online copy on your "${accountName}" account.`,
58-
commonConfiguration: {
59-
title: "Common configuration",
60-
description: "Basic permissions required for copy operations",
58+
crossAccountConfiguration: {
59+
title: "Cross-account container copy",
60+
description: (sourceAccount: string, destinationAccount: string) =>
61+
`Please follow the instruction below to grant requisite permissions to copy data from "${sourceAccount}" to "${destinationAccount}".`,
6162
},
6263
onlineConfiguration: {
63-
title: "Online copy configuration",
64-
description: "Additional permissions required for online copy operations",
64+
title: "Online container copy",
65+
description: (accountName: string) =>
66+
`Please follow the instructions below to enable online copy on your "${accountName}" account.`,
6567
},
6668
},
6769
toggleBtn: {
@@ -129,10 +131,17 @@ export default {
129131
},
130132
onlineCopyEnabled: {
131133
title: "Online copy enabled",
132-
description: (accountName: string) => `Enable Online copy on "${accountName}".`,
134+
description: (accountName: string) =>
135+
`Enable online container copy by clicking the button below on your "${accountName}" account.`,
133136
hrefText: "Learn more about online copy jobs",
134137
href: "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
135138
buttonText: "Enable Online Copy",
139+
validateAllVersionsAndDeletesChangeFeedSpinnerLabel:
140+
"Validating All versions and deletes change feed mode (preview)...",
141+
enablingAllVersionsAndDeletesChangeFeedSpinnerLabel:
142+
"Enabling All versions and deletes change feed mode (preview)...",
143+
enablingOnlineCopySpinnerLabel: (accountName: string) =>
144+
`Enabling online copy on your "${accountName}" account ...`,
136145
},
137146
MonitorJobs: {
138147
Columns: {

src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddReadPermissionToDefaultIdentity.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Link, Stack, Text, Toggle } from "@fluentui/react";
2-
import React, { useCallback } from "react";
2+
import React from "react";
33
import { logError } from "../../../../../Common/Logger";
44
import { assignRole } from "../../../../../Utils/arm/RbacUtils";
55
import ContainerCopyMessages from "../../../ContainerCopyMessages";
@@ -25,7 +25,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
2525
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
2626
const [readPermissionAssigned, onToggle] = useToggle(false);
2727

28-
const handleAddReadPermission = useCallback(async () => {
28+
const handleAddReadPermission = async () => {
2929
const { source, target } = copyJobState;
3030
const selectedSourceAccount = source?.account;
3131
try {
@@ -56,7 +56,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
5656
} finally {
5757
setLoading(false);
5858
}
59-
}, [copyJobState, setCopyJobState, setContextError]);
59+
};
6060

6161
return (
6262
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>

src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const validatorFn: AccountValidatorFn = (prev: DatabaseAccount, next: DatabaseAc
2020

2121
const OnlineCopyEnabled: React.FC = () => {
2222
const [loading, setLoading] = React.useState(false);
23+
const [loaderMessage, setLoaderMessage] = React.useState("");
2324
const [showRefreshButton, setShowRefreshButton] = React.useState(false);
2425
const intervalRef = React.useRef<NodeJS.Timeout | null>(null);
2526
const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
@@ -75,6 +76,21 @@ const OnlineCopyEnabled: React.FC = () => {
7576
setShowRefreshButton(false);
7677

7778
try {
79+
setLoaderMessage(ContainerCopyMessages.onlineCopyEnabled.validateAllVersionsAndDeletesChangeFeedSpinnerLabel);
80+
const sourAccountBeforeUpdate = await fetchDatabaseAccount(
81+
sourceSubscriptionId,
82+
sourceResourceGroup,
83+
sourceAccountName,
84+
);
85+
if (!sourAccountBeforeUpdate?.properties.enableAllVersionsAndDeletesChangeFeed) {
86+
setLoaderMessage(ContainerCopyMessages.onlineCopyEnabled.enablingAllVersionsAndDeletesChangeFeedSpinnerLabel);
87+
await updateDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName, {
88+
properties: {
89+
enableAllVersionsAndDeletesChangeFeed: true,
90+
},
91+
});
92+
}
93+
setLoaderMessage(ContainerCopyMessages.onlineCopyEnabled.enablingOnlineCopySpinnerLabel(sourceAccountName));
7894
await updateDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName, {
7995
properties: {
8096
enableAllVersionsAndDeletesChangeFeed: true,
@@ -120,7 +136,7 @@ const OnlineCopyEnabled: React.FC = () => {
120136

121137
return (
122138
<Stack className="onlineCopyContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
123-
<LoadingOverlay isLoading={loading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
139+
<LoadingOverlay isLoading={loading} label={loaderMessage} />
124140
<Stack.Item className="info-message">
125141
{ContainerCopyMessages.onlineCopyEnabled.description(source?.account?.name || "")}&ensp;
126142
<Link href={ContainerCopyMessages.onlineCopyEnabled.href} target="_blank" rel="noopener noreferrer">

src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useManagedIdentity.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@ const useManagedIdentity = (
4444
const errorMessage = error.message || "Error enabling system-assigned managed identity. Please try again later.";
4545
logError(errorMessage, "CopyJob/useManagedIdentity.handleAddSystemIdentity");
4646
setContextError(errorMessage);
47-
} finally {
4847
setLoading(false);
4948
}
50-
}, [copyJobState, updateIdentityFn, setCopyJobState]);
49+
}, [copyJobState?.target?.account?.id, updateIdentityFn, setCopyJobState]);
5150

5251
return { loading, handleAddSystemIdentity };
5352
};

src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,23 +186,28 @@ const usePermissionSections = (state: CopyJobContextState): PermissionGroupConfi
186186

187187
const groupsToValidate = useMemo(() => {
188188
const isSameAccount = isIntraAccountCopy(sourceAccount.accountId, targetAccount.accountId);
189-
const commonSections = isSameAccount ? [] : [...PERMISSION_SECTIONS_CONFIG];
189+
const crossAccountSections = isSameAccount ? [] : [...PERMISSION_SECTIONS_CONFIG];
190190
const groups: PermissionGroupConfig[] = [];
191+
const sourceAccountName = state.source?.account?.name || "";
192+
const targetAccountName = state.target?.account?.name || "";
191193

192-
if (commonSections.length > 0) {
194+
if (crossAccountSections.length > 0) {
193195
groups.push({
194-
id: "commonConfigs",
195-
title: ContainerCopyMessages.assignPermissions.commonConfiguration.title,
196-
description: ContainerCopyMessages.assignPermissions.commonConfiguration.description,
197-
sections: commonSections,
196+
id: "crossAccountConfigs",
197+
title: ContainerCopyMessages.assignPermissions.crossAccountConfiguration.title,
198+
description: ContainerCopyMessages.assignPermissions.crossAccountConfiguration.description(
199+
sourceAccountName,
200+
targetAccountName,
201+
),
202+
sections: crossAccountSections,
198203
});
199204
}
200205

201206
if (state.migrationType === CopyJobMigrationType.Online) {
202207
groups.push({
203208
id: "onlineConfigs",
204209
title: ContainerCopyMessages.assignPermissions.onlineConfiguration.title,
205-
description: ContainerCopyMessages.assignPermissions.onlineConfiguration.description,
210+
description: ContainerCopyMessages.assignPermissions.onlineConfiguration.description(sourceAccountName),
206211
sections: [...PERMISSION_SECTIONS_FOR_ONLINE_JOBS],
207212
});
208213
}

src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx

Lines changed: 47 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,19 @@ export function useDropdownOptions(
1111
subscriptionOptions: DropdownOptionType[];
1212
accountOptions: DropdownOptionType[];
1313
} {
14-
const subscriptionOptions = React.useMemo(
15-
() =>
16-
subscriptions?.map((sub) => ({
17-
key: sub.subscriptionId,
18-
text: sub.displayName,
19-
data: sub,
20-
})) || [],
21-
[subscriptions],
22-
);
14+
const subscriptionOptions =
15+
subscriptions?.map((sub) => ({
16+
key: sub.subscriptionId,
17+
text: sub.displayName,
18+
data: sub,
19+
})) || [];
2320

24-
const accountOptions = React.useMemo(
25-
() =>
26-
accounts?.map((account) => ({
27-
key: account.id,
28-
text: account.name,
29-
data: account,
30-
})) || [],
31-
[accounts],
32-
);
21+
const accountOptions =
22+
accounts?.map((account) => ({
23+
key: account.id,
24+
text: account.name,
25+
data: account,
26+
})) || [];
3327

3428
return { subscriptionOptions, accountOptions };
3529
}
@@ -38,45 +32,42 @@ type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
3832

3933
export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
4034
const { setValidationCache } = useCopyJobPrerequisitesCache();
41-
const handleSelectSourceAccount = React.useCallback(
42-
(type: "subscription" | "account", data: (Subscription & DatabaseAccount) | undefined) => {
43-
setCopyJobState((prevState: CopyJobContextState) => {
44-
if (type === "subscription") {
45-
return {
46-
...prevState,
47-
source: {
48-
...prevState.source,
49-
subscription: data || null,
50-
account: null,
51-
},
52-
};
53-
}
54-
if (type === "account") {
55-
return {
56-
...prevState,
57-
source: {
58-
...prevState.source,
59-
account: data || null,
60-
},
61-
};
62-
}
63-
return prevState;
64-
});
65-
setValidationCache(new Map<string, boolean>());
66-
},
67-
[setCopyJobState, setValidationCache],
68-
);
35+
const handleSelectSourceAccount = (
36+
type: "subscription" | "account",
37+
data: (Subscription & DatabaseAccount) | undefined,
38+
) => {
39+
setCopyJobState((prevState: CopyJobContextState) => {
40+
if (type === "subscription") {
41+
return {
42+
...prevState,
43+
source: {
44+
...prevState.source,
45+
subscription: data || null,
46+
account: null,
47+
},
48+
};
49+
}
50+
if (type === "account") {
51+
return {
52+
...prevState,
53+
source: {
54+
...prevState.source,
55+
account: data || null,
56+
},
57+
};
58+
}
59+
return prevState;
60+
});
61+
setValidationCache(new Map<string, boolean>());
62+
};
6963

70-
const handleMigrationTypeChange = React.useCallback(
71-
(_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
72-
setCopyJobState((prevState: CopyJobContextState) => ({
73-
...prevState,
74-
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
75-
}));
76-
setValidationCache(new Map<string, boolean>());
77-
},
78-
[setCopyJobState, setValidationCache],
79-
);
64+
const handleMigrationTypeChange = React.useCallback((_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
65+
setCopyJobState((prevState: CopyJobContextState) => ({
66+
...prevState,
67+
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
68+
}));
69+
setValidationCache(new Map<string, boolean>());
70+
}, []);
8071

8172
return { handleSelectSourceAccount, handleMigrationTypeChange };
8273
}

src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectSourceAndTargetContainers/SelectSourceAndTargetContainers.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import ContainerCopyMessages from "../../../ContainerCopyMessages";
77
import { useCopyJobContext } from "../../../Context/CopyJobContext";
88
import { DatabaseContainerSection } from "./components/DatabaseContainerSection";
99
import { dropDownChangeHandler } from "./Events/DropDownChangeHandler";
10-
import { useMemoizedSourceAndTargetData } from "./memoizedData";
10+
import { useSourceAndTargetData } from "./memoizedData";
1111

1212
type SelectSourceAndTargetContainers = {
1313
showAddCollectionPanel?: () => void;
@@ -16,31 +16,35 @@ type SelectSourceAndTargetContainers = {
1616
const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourceAndTargetContainers) => {
1717
const { copyJobState, setCopyJobState } = useCopyJobContext();
1818
const { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams } =
19-
useMemoizedSourceAndTargetData(copyJobState);
19+
useSourceAndTargetData(copyJobState);
2020

21-
const sourceDatabases = useDatabases(...sourceDbParams) || [];
22-
const sourceContainers = useDataContainers(...sourceContainerParams) || [];
23-
const targetDatabases = useDatabases(...targetDbParams) || [];
24-
const targetContainers = useDataContainers(...targetContainerParams) || [];
21+
if (!source) {
22+
return null;
23+
}
24+
25+
const sourceDatabases = useDatabases(...sourceDbParams);
26+
const sourceContainers = useDataContainers(...sourceContainerParams);
27+
const targetDatabases = useDatabases(...targetDbParams);
28+
const targetContainers = useDataContainers(...targetContainerParams);
2529

2630
const sourceDatabaseOptions = React.useMemo(
27-
() => sourceDatabases.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })),
31+
() => sourceDatabases?.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })) || [],
2832
[sourceDatabases],
2933
);
3034
const sourceContainerOptions = React.useMemo(
31-
() => sourceContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })),
35+
() => sourceContainers?.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })) || [],
3236
[sourceContainers],
3337
);
3438
const targetDatabaseOptions = React.useMemo(
35-
() => targetDatabases.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })),
39+
() => targetDatabases?.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })) || [],
3640
[targetDatabases],
3741
);
3842
const targetContainerOptions = React.useMemo(
39-
() => targetContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })),
43+
() => targetContainers?.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })) || [],
4044
[targetContainers],
4145
);
4246

43-
const onDropdownChange = React.useCallback(dropDownChangeHandler(setCopyJobState), [setCopyJobState]);
47+
const onDropdownChange = dropDownChangeHandler(setCopyJobState);
4448

4549
return (
4650
<Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>

0 commit comments

Comments
 (0)