removal of multiple useMemo & useCallbacks to reduce the over optimization

This commit is contained in:
Bikram Choudhury
2025-11-07 18:31:40 +05:30
parent 490309b403
commit a537d958ca
15 changed files with 72 additions and 91 deletions

View File

@@ -44,8 +44,8 @@ export const getDatabaseEndpoint = (apiType: ApiType): string => {
return "gremlinDatabases"; return "gremlinDatabases";
case "Tables": case "Tables":
return "tables"; return "tables";
default:
case "SQL": case "SQL":
default:
return "sqlDatabases"; return "sqlDatabases";
} }
}; };
@@ -58,8 +58,8 @@ export const getCollectionEndpoint = (apiType: ApiType): string => {
return "tables"; return "tables";
case "Gremlin": case "Gremlin":
return "graphs"; return "graphs";
default:
case "SQL": case "SQL":
default:
return "containers"; return "containers";
} }
}; };

View File

@@ -74,7 +74,6 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
} }
copyJobsAbortController = null; copyJobsAbortController = null;
/* added a lower bound to "0" and upper bound to "100" */
const calculateCompletionPercentage = (processed: number, total: number): number => { const calculateCompletionPercentage = (processed: number, total: number): number => {
if ( if (
typeof processed !== "number" || typeof processed !== "number" ||

View File

@@ -1,5 +1,5 @@
import { DatabaseAccount } from "Contracts/DataModels"; import { DatabaseAccount } from "Contracts/DataModels";
import { CopyJobErrorType } from "./Types/CopyJobTypes"; import { CopyJobErrorType, CopyJobType } from "./Types/CopyJobTypes";
const azurePortalMpacEndpoint = "https://ms.portal.azure.com/"; const azurePortalMpacEndpoint = "https://ms.portal.azure.com/";
@@ -124,3 +124,15 @@ export function isIntraAccountCopy(sourceAccountId: string | undefined, targetAc
sourceAccountDetails?.accountName === targetAccountDetails?.accountName sourceAccountDetails?.accountName === targetAccountDetails?.accountName
); );
} }
export function isEqual(prevJobs: CopyJobType[], newJobs: CopyJobType[]): boolean {
if (prevJobs.length !== newJobs.length) {
return false;
}
return prevJobs.every((prevJob: CopyJobType) => {
const newJob = newJobs.find((job) => job.Name === prevJob.Name);
if (!newJob) {
return false;
}
return prevJob.Status === newJob.Status;
});
}

View File

@@ -1,5 +1,5 @@
import { Link, Stack, Text, Toggle } from "@fluentui/react"; import { Link, Stack, Text, Toggle } from "@fluentui/react";
import React, { useCallback } from "react"; import React from "react";
import { logError } from "../../../../../Common/Logger"; import { logError } from "../../../../../Common/Logger";
import { assignRole } from "../../../../../Utils/arm/RbacUtils"; import { assignRole } from "../../../../../Utils/arm/RbacUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages"; import ContainerCopyMessages from "../../../ContainerCopyMessages";
@@ -25,7 +25,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext(); const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
const [readPermissionAssigned, onToggle] = useToggle(false); const [readPermissionAssigned, onToggle] = useToggle(false);
const handleAddReadPermission = useCallback(async () => { const handleAddReadPermission = async () => {
const { source, target } = copyJobState; const { source, target } = copyJobState;
const selectedSourceAccount = source?.account; const selectedSourceAccount = source?.account;
try { try {
@@ -56,7 +56,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [copyJobState, setCopyJobState, setContextError]); };
return ( return (
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}> <Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>

View File

@@ -47,7 +47,7 @@ const useManagedIdentity = (
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [copyJobState, updateIdentityFn, setCopyJobState]); }, [updateIdentityFn]);
return { loading, handleAddSystemIdentity }; return { loading, handleAddSystemIdentity };
}; };

View File

@@ -26,5 +26,5 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = React.memo(
onChange={onChange} onChange={onChange}
/> />
</FieldRow> </FieldRow>
), ), (prev, next) => prev.options.length === next.options.length
); );

View File

@@ -24,5 +24,5 @@ export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.m
onChange={onChange} onChange={onChange}
/> />
</FieldRow> </FieldRow>
), ), (prev, next) => prev.options.length === next.options.length
); );

View File

@@ -11,25 +11,17 @@ export function useDropdownOptions(
subscriptionOptions: DropdownOptionType[]; subscriptionOptions: DropdownOptionType[];
accountOptions: DropdownOptionType[]; accountOptions: DropdownOptionType[];
} { } {
const subscriptionOptions = React.useMemo( const subscriptionOptions = subscriptions?.map((sub) => ({
() => key: sub.subscriptionId,
subscriptions?.map((sub) => ({ text: sub.displayName,
key: sub.subscriptionId, data: sub,
text: sub.displayName, })) || [];
data: sub,
})) || [],
[subscriptions],
);
const accountOptions = React.useMemo( const accountOptions = accounts?.map((account) => ({
() => key: account.id,
accounts?.map((account) => ({ text: account.name,
key: account.id, data: account,
text: account.name, })) || [];
data: account,
})) || [],
[accounts],
);
return { subscriptionOptions, accountOptions }; return { subscriptionOptions, accountOptions };
} }
@@ -38,8 +30,7 @@ type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
export function useEventHandlers(setCopyJobState: setCopyJobStateType) { export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
const { setValidationCache } = useCopyJobPrerequisitesCache(); const { setValidationCache } = useCopyJobPrerequisitesCache();
const handleSelectSourceAccount = React.useCallback( const handleSelectSourceAccount = (type: "subscription" | "account", data: (Subscription & DatabaseAccount) | undefined) => {
(type: "subscription" | "account", data: (Subscription & DatabaseAccount) | undefined) => {
setCopyJobState((prevState: CopyJobContextState) => { setCopyJobState((prevState: CopyJobContextState) => {
if (type === "subscription") { if (type === "subscription") {
return { return {
@@ -63,9 +54,7 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
return prevState; return prevState;
}); });
setValidationCache(new Map<string, boolean>()); setValidationCache(new Map<string, boolean>());
}, };
[setCopyJobState, setValidationCache],
);
const handleMigrationTypeChange = React.useCallback( const handleMigrationTypeChange = React.useCallback(
(_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => { (_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
@@ -75,7 +64,7 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
})); }));
setValidationCache(new Map<string, boolean>()); setValidationCache(new Map<string, boolean>());
}, },
[setCopyJobState, setValidationCache], [],
); );
return { handleSelectSourceAccount, handleMigrationTypeChange }; return { handleSelectSourceAccount, handleMigrationTypeChange };

View File

@@ -7,36 +7,40 @@ import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext"; import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { DatabaseContainerSection } from "./components/DatabaseContainerSection"; import { DatabaseContainerSection } from "./components/DatabaseContainerSection";
import { dropDownChangeHandler } from "./Events/DropDownChangeHandler"; import { dropDownChangeHandler } from "./Events/DropDownChangeHandler";
import { useMemoizedSourceAndTargetData } from "./memoizedData"; import { useSourceAndTargetData } from "./memoizedData";
const SelectSourceAndTargetContainers = () => { const SelectSourceAndTargetContainers = () => {
const { copyJobState, setCopyJobState } = useCopyJobContext(); const { copyJobState, setCopyJobState } = useCopyJobContext();
const { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams } = const { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams } =
useMemoizedSourceAndTargetData(copyJobState); useSourceAndTargetData(copyJobState);
const sourceDatabases = useDatabases(...sourceDbParams) || []; if(!source) {
const sourceContainers = useDataContainers(...sourceContainerParams) || []; return null;
const targetDatabases = useDatabases(...targetDbParams) || []; }
const targetContainers = useDataContainers(...targetContainerParams) || [];
const sourceDatabases = useDatabases(...sourceDbParams);
const sourceContainers = useDataContainers(...sourceContainerParams);
const targetDatabases = useDatabases(...targetDbParams);
const targetContainers = useDataContainers(...targetContainerParams);
const sourceDatabaseOptions = React.useMemo( const sourceDatabaseOptions = React.useMemo(
() => sourceDatabases.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })), () => sourceDatabases?.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })) || [],
[sourceDatabases], [sourceDatabases],
); );
const sourceContainerOptions = React.useMemo( const sourceContainerOptions = React.useMemo(
() => sourceContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })), () => sourceContainers?.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })) || [],
[sourceContainers], [sourceContainers],
); );
const targetDatabaseOptions = React.useMemo( const targetDatabaseOptions = React.useMemo(
() => targetDatabases.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })), () => targetDatabases?.map((db: DatabaseModel) => ({ key: db.name, text: db.name, data: db })) || [],
[targetDatabases], [targetDatabases],
); );
const targetContainerOptions = React.useMemo( const targetContainerOptions = React.useMemo(
() => targetContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })), () => targetContainers?.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })) || [],
[targetContainers], [targetContainers],
); );
const onDropdownChange = React.useCallback(dropDownChangeHandler(setCopyJobState), [setCopyJobState]); const onDropdownChange = dropDownChangeHandler(setCopyJobState);
return ( return (
<Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}> <Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>

View File

@@ -1,8 +1,7 @@
import React from "react";
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils"; import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
import { CopyJobContextState, DatabaseParams, DataContainerParams } from "../../../Types/CopyJobTypes"; import { CopyJobContextState, DatabaseParams, DataContainerParams } from "../../../Types/CopyJobTypes";
export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState) { export function useSourceAndTargetData(copyJobState: CopyJobContextState) {
const { source, target } = copyJobState ?? {}; const { source, target } = copyJobState ?? {};
const selectedSourceAccount = source?.account; const selectedSourceAccount = source?.account;
const selectedTargetAccount = target?.account; const selectedTargetAccount = target?.account;
@@ -17,27 +16,10 @@ export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState
accountName: targetAccountName, accountName: targetAccountName,
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id); } = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
const sourceDbParams = React.useMemo( const sourceDbParams = [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, "SQL"] as DatabaseParams;
() => [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, "SQL"] as DatabaseParams, const sourceContainerParams = [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, source?.databaseId, "SQL"] as DataContainerParams;
[sourceSubscriptionId, sourceResourceGroup, sourceAccountName], const targetDbParams = [targetSubscriptionId, targetResourceGroup, targetAccountName, "SQL"] as DatabaseParams;
); const targetContainerParams = [targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId, "SQL"] as DataContainerParams;
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 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 };
} }

View File

@@ -85,3 +85,4 @@ function useCreateCopyJobScreensList() {
} }
export { SCREEN_KEYS, useCreateCopyJobScreensList }; export { SCREEN_KEYS, useCreateCopyJobScreensList };

View File

@@ -34,7 +34,7 @@ const iconMap: Partial<Record<CopyJobStatusType, string>> = {
[CopyJobStatusType.Completed]: "CompletedSolid", [CopyJobStatusType.Completed]: "CompletedSolid",
}; };
const CopyJobStatusWithIcon: React.FC<{ status: CopyJobStatusType }> = ({ status }) => { const CopyJobStatusWithIcon: React.FC<{ status: CopyJobStatusType }> = React.memo(({ status }) => {
const statusText = ContainerCopyMessages.MonitorJobs.Status[status] || "Unknown"; const statusText = ContainerCopyMessages.MonitorJobs.Status[status] || "Unknown";
const isSpinnerStatus = [ const isSpinnerStatus = [
@@ -57,6 +57,6 @@ const CopyJobStatusWithIcon: React.FC<{ status: CopyJobStatusType }> = ({ status
<Text>{statusText}</Text> <Text>{statusText}</Text>
</Stack> </Stack>
); );
}; });
export default CopyJobStatusWithIcon; export default CopyJobStatusWithIcon;

View File

@@ -1,5 +1,5 @@
import { ActionButton, Image } from "@fluentui/react"; import { ActionButton, Image } from "@fluentui/react";
import React, { useCallback } from "react"; import React, { memo } from "react";
import CopyJobIcon from "../../../../../images/ContainerCopy/copy-jobs.svg"; import CopyJobIcon from "../../../../../images/ContainerCopy/copy-jobs.svg";
import * as Actions from "../../Actions/CopyJobActions"; import * as Actions from "../../Actions/CopyJobActions";
import ContainerCopyMessages from "../../ContainerCopyMessages"; import ContainerCopyMessages from "../../ContainerCopyMessages";
@@ -7,16 +7,15 @@ import ContainerCopyMessages from "../../ContainerCopyMessages";
interface CopyJobsNotFoundProps {} interface CopyJobsNotFoundProps {}
const CopyJobsNotFound: React.FC<CopyJobsNotFoundProps> = () => { const CopyJobsNotFound: React.FC<CopyJobsNotFoundProps> = () => {
const handleCreateCopyJob = useCallback(Actions.openCreateCopyJobPanel, []);
return ( return (
<div className="notFoundContainer flexContainer centerContent"> <div className="notFoundContainer flexContainer centerContent">
<Image src={CopyJobIcon} alt={ContainerCopyMessages.noCopyJobsTitle} width={100} height={100} /> <Image src={CopyJobIcon} alt={ContainerCopyMessages.noCopyJobsTitle} width={100} height={100} />
<h4 className="noCopyJobsMessage">{ContainerCopyMessages.noCopyJobsTitle}</h4> <h4 className="noCopyJobsMessage">{ContainerCopyMessages.noCopyJobsTitle}</h4>
<ActionButton allowDisabledFocus className="createCopyJobButton" onClick={handleCreateCopyJob}> <ActionButton allowDisabledFocus className="createCopyJobButton" onClick={Actions.openCreateCopyJobPanel}>
{ContainerCopyMessages.createCopyJobButtonText} {ContainerCopyMessages.createCopyJobButtonText}
</ActionButton> </ActionButton>
</div> </div>
); );
}; };
export default CopyJobsNotFound; export default memo(CopyJobsNotFound);

View File

@@ -58,22 +58,19 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
setStartIndex(0); setStartIndex(0);
}; };
const columns: IColumn[] = React.useMemo( const columns: IColumn[] = getColumns(handleSort, handleActionClick, sortedColumnKey, isSortedDescending);
() => getColumns(handleSort, handleActionClick, sortedColumnKey, isSortedDescending),
[handleSort, handleActionClick, sortedColumnKey, isSortedDescending],
);
const _handleRowClick = React.useCallback((job: CopyJobType) => { const _handleRowClick = (job: CopyJobType) => {
openCopyJobDetailsPanel(job); openCopyJobDetailsPanel(job);
}, []); };
const _onRenderRow = React.useCallback((props: any) => { const _onRenderRow = (props: any) => {
return ( return (
<div onClick={_handleRowClick.bind(null, props.item)}> <div onClick={_handleRowClick.bind(null, props.item)}>
<DetailsRow {...props} styles={{ root: { cursor: "pointer" } }} /> <DetailsRow {...props} styles={{ root: { cursor: "pointer" } }} />
</div> </div>
); );
}, []); };
return ( return (
<div style={styles.container}> <div style={styles.container}>

View File

@@ -3,13 +3,14 @@ import { MessageBar, MessageBarType, Stack } from "@fluentui/react";
import ShimmerTree, { IndentLevel } from "Common/ShimmerTree/ShimmerTree"; import ShimmerTree, { IndentLevel } from "Common/ShimmerTree/ShimmerTree";
import React, { forwardRef, useEffect, useImperativeHandle } from "react"; import React, { forwardRef, useEffect, useImperativeHandle } from "react";
import { getCopyJobs, updateCopyJobStatus } from "../Actions/CopyJobActions"; import { getCopyJobs, updateCopyJobStatus } from "../Actions/CopyJobActions";
import { convertToCamelCase } from "../CopyJobUtils"; import { convertToCamelCase, isEqual } from "../CopyJobUtils";
import { CopyJobStatusType } from "../Enums/CopyJobEnums"; import { CopyJobStatusType } from "../Enums/CopyJobEnums";
import CopyJobsNotFound from "../MonitorCopyJobs/Components/CopyJobs.NotFound"; import CopyJobsNotFound from "../MonitorCopyJobs/Components/CopyJobs.NotFound";
import { CopyJobType, JobActionUpdatorType } from "../Types/CopyJobTypes"; import { CopyJobType, JobActionUpdatorType } from "../Types/CopyJobTypes";
import CopyJobsList from "./Components/CopyJobsList"; import CopyJobsList from "./Components/CopyJobsList";
const FETCH_INTERVAL_MS = 30 * 1000; const FETCH_INTERVAL_MS = 30 * 1000;
const SHIMMER_INDENT_LEVELS: IndentLevel[] = Array(7).fill({ level: 0, width: "100%" });
interface MonitorCopyJobsProps {} interface MonitorCopyJobsProps {}
@@ -24,8 +25,6 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
const isUpdatingRef = React.useRef(false); const isUpdatingRef = React.useRef(false);
const isFirstFetchRef = React.useRef(true); const isFirstFetchRef = React.useRef(true);
const indentLevels = React.useMemo<IndentLevel[]>(() => Array(7).fill({ level: 0, width: "100%" }), []);
const fetchJobs = React.useCallback(async () => { const fetchJobs = React.useCallback(async () => {
if (isUpdatingRef.current) { if (isUpdatingRef.current) {
return; return;
@@ -38,8 +37,7 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
const response = await getCopyJobs(); const response = await getCopyJobs();
setJobs((prevJobs) => { setJobs((prevJobs) => {
const isSame = JSON.stringify(prevJobs) === JSON.stringify(response); return isEqual(prevJobs, response) ? prevJobs : response;
return isSame ? prevJobs : response;
}); });
} catch (error) { } catch (error) {
setError(error.message || "Failed to load copy jobs. Please try again later."); setError(error.message || "Failed to load copy jobs. Please try again later.");
@@ -96,7 +94,7 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
[], [],
); );
const memoizedJobsList = React.useMemo(() => { const renderJobsList = () => {
if (loading) { if (loading) {
return null; return null;
} }
@@ -104,17 +102,17 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
return <CopyJobsList jobs={jobs} handleActionClick={handleActionClick} />; return <CopyJobsList jobs={jobs} handleActionClick={handleActionClick} />;
} }
return <CopyJobsNotFound />; return <CopyJobsNotFound />;
}, [jobs, loading, handleActionClick]); };
return ( return (
<Stack className="monitorCopyJobs flexContainer"> <Stack className="monitorCopyJobs flexContainer">
{loading && <ShimmerTree indentLevels={indentLevels} style={{ width: "100%", padding: "1rem 2.5rem" }} />} {loading && <ShimmerTree indentLevels={SHIMMER_INDENT_LEVELS} style={{ width: "100%", padding: "1rem 2.5rem" }} />}
{error && ( {error && (
<MessageBar messageBarType={MessageBarType.error} isMultiline={false} onDismiss={() => setError(null)}> <MessageBar messageBarType={MessageBarType.error} isMultiline={false} onDismiss={() => setError(null)}>
{error} {error}
</MessageBar> </MessageBar>
)} )}
{memoizedJobsList} {renderJobsList()}
</Stack> </Stack>
); );
}); });