mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-14 00:17:36 +01:00
Fix lint & typescript checks
This commit is contained in:
@@ -2,8 +2,8 @@ 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[];
|
||||
@@ -32,9 +32,7 @@ const ShimmerTree = ({ indentLevels, style = {} }: ShimmerTreeProps) => {
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 8 }} style={{ width: "50%", ...style }} data-testid="shimmer-stack">
|
||||
{
|
||||
indentLevels.map((indentLevel: IndentLevel) => renderShimmers(indentLevel))
|
||||
}
|
||||
{indentLevels.map((indentLevel: IndentLevel) => renderShimmers(indentLevel))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,9 +7,12 @@ import {
|
||||
create,
|
||||
listByDatabaseAccount,
|
||||
pause,
|
||||
resume
|
||||
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,
|
||||
@@ -17,7 +20,7 @@ import {
|
||||
COSMOS_SQL_COMPONENT,
|
||||
extractErrorMessage,
|
||||
formatUTCDateTime,
|
||||
getAccountDetailsFromResourceId
|
||||
getAccountDetailsFromResourceId,
|
||||
} from "../CopyJobUtils";
|
||||
import CreateCopyJobScreensProvider from "../CreateCopyJob/Screens/CreateCopyJobScreensProvider";
|
||||
import { CopyJobActions, CopyJobStatusType } from "../Enums";
|
||||
@@ -25,14 +28,14 @@ import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefSta
|
||||
import { CopyJobContextState, CopyJobError, CopyJobErrorType, CopyJobType } from "../Types";
|
||||
|
||||
export const openCreateCopyJobPanel = () => {
|
||||
const sidePanelState = useSidePanel.getState()
|
||||
const sidePanelState = useSidePanel.getState();
|
||||
sidePanelState.setPanelHasConsole(false);
|
||||
sidePanelState.openSidePanel(
|
||||
ContainerCopyMessages.createCopyJobPanelTitle,
|
||||
<CreateCopyJobScreensProvider />,
|
||||
"650px"
|
||||
"650px",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let copyJobsAbortController: AbortController | null = null;
|
||||
|
||||
@@ -43,12 +46,14 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
||||
}
|
||||
copyJobsAbortController = new AbortController();
|
||||
try {
|
||||
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(userContext.databaseAccount?.id || "");
|
||||
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(
|
||||
userContext.databaseAccount?.id || "",
|
||||
);
|
||||
const response = await listByDatabaseAccount(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
copyJobsAbortController.signal
|
||||
copyJobsAbortController.signal,
|
||||
);
|
||||
|
||||
const jobs = response.value || [];
|
||||
@@ -60,8 +65,8 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
||||
/* 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' ||
|
||||
typeof processed !== "number" ||
|
||||
typeof total !== "number" ||
|
||||
!isFinite(processed) ||
|
||||
!isFinite(total) ||
|
||||
total <= 0
|
||||
@@ -74,12 +79,15 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
||||
};
|
||||
|
||||
const formattedJobs: CopyJobType[] = jobs
|
||||
.filter((job: DataTransferJobGetResults) =>
|
||||
.filter(
|
||||
(job: DataTransferJobGetResults) =>
|
||||
job.properties?.source?.component === COSMOS_SQL_COMPONENT &&
|
||||
job.properties?.destination?.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()
|
||||
.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);
|
||||
@@ -102,36 +110,32 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
||||
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 { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(
|
||||
userContext.databaseAccount?.id || "",
|
||||
);
|
||||
const body = {
|
||||
properties: {
|
||||
source: {
|
||||
component: "CosmosDBSql",
|
||||
remoteAccountName: source?.account?.name,
|
||||
databaseName: source?.databaseId,
|
||||
containerName: source?.containerId
|
||||
containerName: source?.containerId,
|
||||
},
|
||||
destination: {
|
||||
component: "CosmosDBSql",
|
||||
databaseName: target?.databaseId,
|
||||
containerName: target?.containerId
|
||||
containerName: target?.containerId,
|
||||
},
|
||||
mode: migrationType,
|
||||
},
|
||||
mode: migrationType
|
||||
}
|
||||
} as unknown as CreateJobRequest;
|
||||
|
||||
const response = await create(
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
accountName,
|
||||
jobName,
|
||||
body,
|
||||
);
|
||||
const response = await create(subscriptionId, resourceGroup, accountName, jobName, body);
|
||||
MonitorCopyJobsRefState.getState().ref?.refreshJobList();
|
||||
onSuccess();
|
||||
return response;
|
||||
@@ -139,11 +143,10 @@ export const submitCreateCopyJob = async (state: CopyJobContextState, onSuccess:
|
||||
console.error("Error submitting create copy job:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCopyJobStatus = async (job: CopyJobType, action: string): Promise<DataTransferJobGetResults> => {
|
||||
try {
|
||||
|
||||
let updateFn = null;
|
||||
switch (action.toLowerCase()) {
|
||||
case CopyJobActions.pause:
|
||||
@@ -161,7 +164,9 @@ export const updateCopyJobStatus = async (job: CopyJobType, action: string): Pro
|
||||
default:
|
||||
throw new Error(`Unsupported action: ${action}`);
|
||||
}
|
||||
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(userContext.databaseAccount?.id || "");
|
||||
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(
|
||||
userContext.databaseAccount?.id || "",
|
||||
);
|
||||
const response = await updateFn?.(subscriptionId, resourceGroup, accountName, job.Name);
|
||||
|
||||
return response;
|
||||
@@ -169,10 +174,13 @@ export const updateCopyJobStatus = async (job: CopyJobType, action: string): Pro
|
||||
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}'`);
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,6 +26,6 @@ const CopyJobCommandBar: React.FC<ContainerCopyProps> = ({ container }) => {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CopyJobCommandBar;
|
||||
@@ -10,7 +10,7 @@ import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefSta
|
||||
import { CopyJobCommandBarBtnType } from "../Types";
|
||||
|
||||
function getCopyJobBtns(): CopyJobCommandBarBtnType[] {
|
||||
const monitorCopyJobsRef = MonitorCopyJobsRefState(state => state.ref);
|
||||
const monitorCopyJobsRef = MonitorCopyJobsRefState((state) => state.ref);
|
||||
const buttons: CopyJobCommandBarBtnType[] = [
|
||||
{
|
||||
key: "createCopyJob",
|
||||
@@ -33,7 +33,7 @@ function getCopyJobBtns(): CopyJobCommandBarBtnType[] {
|
||||
iconSrc: FeedbackIcon,
|
||||
label: ContainerCopyMessages.feedbackButtonLabel,
|
||||
ariaLabel: ContainerCopyMessages.feedbackButtonAriaLabel,
|
||||
onClick: () => { },
|
||||
onClick: () => {},
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
@@ -52,7 +52,7 @@ function btnMapper(config: CopyJobCommandBarBtnType): CommandButtonComponentProp
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function getCommandBarButtons(_container: Explorer): CommandButtonComponentProps[] {
|
||||
return getCopyJobBtns().map(btnMapper);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ export default {
|
||||
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.",
|
||||
selectSourceAndTargetContainersDescription:
|
||||
"Please select a source container and a destination container to copy to.",
|
||||
sourceContainerSubHeading: "Source container",
|
||||
targetContainerSubHeading: "Destination container",
|
||||
databaseDropdownLabel: "Database",
|
||||
@@ -42,45 +43,59 @@ export default {
|
||||
|
||||
// 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."
|
||||
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"
|
||||
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.",
|
||||
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.",
|
||||
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}'?` : "",
|
||||
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.",
|
||||
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.",
|
||||
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.",
|
||||
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.",
|
||||
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.",
|
||||
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.",
|
||||
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: {
|
||||
@@ -112,6 +127,6 @@ export default {
|
||||
Faulted: "Failed",
|
||||
Skipped: "Cancelled",
|
||||
Cancelled: "Cancelled",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export const useCopyJobContext = (): CopyJobContextProviderType => {
|
||||
throw new Error("useCopyJobContext must be used within a CopyJobContextProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
};
|
||||
|
||||
interface CopyJobContextProviderProps {
|
||||
children: React.ReactNode;
|
||||
@@ -32,9 +32,9 @@ const getInitialCopyJobState = (): CopyJobContextState => {
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
sourceReadAccessFromTarget: false
|
||||
}
|
||||
}
|
||||
sourceReadAccessFromTarget: false,
|
||||
};
|
||||
};
|
||||
|
||||
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
|
||||
const [copyJobState, setCopyJobState] = React.useState<CopyJobContextState>(getInitialCopyJobState());
|
||||
@@ -42,13 +42,13 @@ const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) =>
|
||||
|
||||
const resetCopyJobState = () => {
|
||||
setCopyJobState(getInitialCopyJobState());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CopyJobContext.Provider value={{ copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
|
||||
{props.children}
|
||||
</CopyJobContext.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CopyJobContextProvider;
|
||||
@@ -5,7 +5,7 @@ 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}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const COSMOS_SQL_COMPONENT = "CosmosDBSql";
|
||||
|
||||
@@ -25,8 +25,12 @@ export function buildDataTransferJobPath({
|
||||
action?: string;
|
||||
}) {
|
||||
let path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`;
|
||||
if (jobName) path += `/${jobName}`;
|
||||
if (action) path += `/${action}`;
|
||||
if (jobName) {
|
||||
path += `/${jobName}`;
|
||||
}
|
||||
if (action) {
|
||||
path += `/${action}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -44,16 +48,22 @@ export function convertTime(timeStr: string): string | null {
|
||||
};
|
||||
|
||||
const [hours, minutes, seconds] = timeParts;
|
||||
const formattedTimeParts = [formatPart(hours, "hours"), formatPart(minutes, "minutes"), formatPart(seconds, "seconds")]
|
||||
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
|
||||
}
|
||||
|
||||
export function formatUTCDateTime(utcStr: string): { formattedDateTime: string, timestamp: number } | null {
|
||||
export function formatUTCDateTime(utcStr: string): { formattedDateTime: string; timestamp: number } | null {
|
||||
const date = new Date(utcStr);
|
||||
if (isNaN(date.getTime())) return null;
|
||||
if (isNaN(date.getTime())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
formattedDateTime: new Intl.DateTimeFormat("en-US", {
|
||||
@@ -66,25 +76,30 @@ export function formatUTCDateTime(utcStr: string): { formattedDateTime: string,
|
||||
}
|
||||
|
||||
export function convertToCamelCase(str: string): string {
|
||||
const formattedStr = str.split(/\s+/).map(
|
||||
word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
).join('');
|
||||
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]
|
||||
}
|
||||
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 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 };
|
||||
}
|
||||
+4
-3
@@ -33,7 +33,8 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
<Toggle
|
||||
label={
|
||||
<Text className="toggle-label" style={textStyle}>
|
||||
{ContainerCopyMessages.addManagedIdentity.toggleLabel} <InfoTooltip content={managedIdentityTooltip} />
|
||||
{ContainerCopyMessages.addManagedIdentity.toggleLabel}
|
||||
<InfoTooltip content={managedIdentityTooltip} />
|
||||
</Text>
|
||||
}
|
||||
checked={systemAssigned}
|
||||
@@ -42,7 +43,8 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
onChange={onToggle}
|
||||
/>
|
||||
<Text className="user-assigned-label" style={textStyle}>
|
||||
{ContainerCopyMessages.addManagedIdentity.userAssignedIdentityLabel} <InfoTooltip content={userAssignedTooltip} />
|
||||
{ContainerCopyMessages.addManagedIdentity.userAssignedIdentityLabel}
|
||||
<InfoTooltip content={userAssignedTooltip} />
|
||||
</Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Link href={manageIdentityLink} target="_blank" rel="noopener noreferrer">
|
||||
@@ -55,7 +57,6 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
title={ContainerCopyMessages.addManagedIdentity.enablementTitle}
|
||||
onCancel={() => onToggle(null, false)}
|
||||
onPrimary={handleAddSystemIdentity}
|
||||
|
||||
>
|
||||
{ContainerCopyMessages.addManagedIdentity.enablementDescription(copyJobState.target?.account?.name)}
|
||||
</PopoverMessage>
|
||||
|
||||
+6
-6
@@ -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,7 +10,6 @@ import { PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
||||
import useToggle from "./hooks/useToggle";
|
||||
|
||||
const TooltipContent = ContainerCopyMessages.readPermissionAssigned.tooltip;
|
||||
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: 'inline-block' } };
|
||||
type AddManagedIdentityProps = Partial<PermissionSectionConfig>;
|
||||
|
||||
const AddReadPermissionToDefaultIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
@@ -25,7 +24,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddManagedIdentityProps> = ()
|
||||
const {
|
||||
subscriptionId: sourceSubscriptionId,
|
||||
resourceGroup: sourceResourceGroup,
|
||||
accountName: sourceAccountName
|
||||
accountName: sourceAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
|
||||
|
||||
setLoading(true);
|
||||
@@ -33,7 +32,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddManagedIdentityProps> = ()
|
||||
sourceSubscriptionId,
|
||||
sourceResourceGroup,
|
||||
sourceAccountName,
|
||||
target?.account?.identity?.principalId!,
|
||||
target?.account?.identity?.principalId ?? "",
|
||||
);
|
||||
if (assignedRole) {
|
||||
setCopyJobState((prevState) => ({
|
||||
@@ -46,12 +45,13 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddManagedIdentityProps> = ()
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [copyJobState]);
|
||||
}, [copyJobState, setCopyJobState]);
|
||||
|
||||
return (
|
||||
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
||||
<div className="toggle-label">
|
||||
{ContainerCopyMessages.readPermissionAssigned.description} <InfoTooltip content={TooltipContent} />
|
||||
{ContainerCopyMessages.readPermissionAssigned.description}
|
||||
<InfoTooltip content={TooltipContent} />
|
||||
</div>
|
||||
<Toggle
|
||||
checked={readPermissionAssigned}
|
||||
|
||||
+2
-1
@@ -18,7 +18,8 @@ const DefaultManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
||||
return (
|
||||
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
||||
<div className="toggle-label">
|
||||
{ContainerCopyMessages.defaultManagedIdentity.description} <InfoTooltip content={managedIdentityTooltip} />
|
||||
{ContainerCopyMessages.defaultManagedIdentity.description}
|
||||
<InfoTooltip content={managedIdentityTooltip} />
|
||||
</div>
|
||||
<Toggle
|
||||
checked={defaultSystemAssigned}
|
||||
|
||||
+4
-8
@@ -12,19 +12,15 @@ const OnlineCopyEnabled: React.FC<AddManagedIdentityProps> = () => {
|
||||
const sourceAccountLink = buildResourceLink(source?.account);
|
||||
const onlineCopyUrl = `${sourceAccountLink}/Features`;
|
||||
const onWindowClosed = () => {
|
||||
console.log('Online copy window closed');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Online copy window closed");
|
||||
};
|
||||
const openWindowAndMonitor = useWindowOpenMonitor(onlineCopyUrl, onWindowClosed);
|
||||
|
||||
return (
|
||||
<Stack className="onlineCopyContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
||||
<div className="toggle-label">
|
||||
{ContainerCopyMessages.onlineCopyEnabled.description}
|
||||
</div>
|
||||
<PrimaryButton
|
||||
text={ContainerCopyMessages.onlineCopyEnabled.buttonText}
|
||||
onClick={openWindowAndMonitor}
|
||||
/>
|
||||
<div className="toggle-label">{ContainerCopyMessages.onlineCopyEnabled.description}</div>
|
||||
<PrimaryButton text={ContainerCopyMessages.onlineCopyEnabled.buttonText} onClick={openWindowAndMonitor} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
+5
-11
@@ -20,19 +20,15 @@ const PointInTimeRestore: React.FC<AddManagedIdentityProps> = () => {
|
||||
const {
|
||||
subscriptionId: sourceSubscriptionId,
|
||||
resourceGroup: sourceResourceGroup,
|
||||
accountName: sourceAccountName
|
||||
accountName: sourceAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
|
||||
|
||||
setLoading(true);
|
||||
const account = await fetchDatabaseAccount(
|
||||
sourceSubscriptionId,
|
||||
sourceResourceGroup,
|
||||
sourceAccountName
|
||||
);
|
||||
const account = await fetchDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName);
|
||||
if (account) {
|
||||
setCopyJobState((prevState) => ({
|
||||
...prevState,
|
||||
source: { ...prevState.source, account: account }
|
||||
source: { ...prevState.source, account: account },
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -40,14 +36,12 @@ const PointInTimeRestore: React.FC<AddManagedIdentityProps> = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
const openWindowAndMonitor = useWindowOpenMonitor(pitrUrl, onWindowClosed);
|
||||
|
||||
return (
|
||||
<Stack className="pointInTimeRestoreContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
||||
<div className="toggle-label">
|
||||
{ContainerCopyMessages.pointInTimeRestore.description}
|
||||
</div>
|
||||
<div className="toggle-label">{ContainerCopyMessages.pointInTimeRestore.description}</div>
|
||||
<PrimaryButton
|
||||
text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText}
|
||||
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
|
||||
|
||||
+5
-9
@@ -7,7 +7,7 @@ interface UseManagedIdentityUpdaterParams {
|
||||
updateIdentityFn: (
|
||||
subscriptionId: string,
|
||||
resourceGroup?: string,
|
||||
accountName?: string
|
||||
accountName?: string,
|
||||
) => Promise<DatabaseAccount | undefined>;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ interface UseManagedIdentityUpdaterReturn {
|
||||
}
|
||||
|
||||
const useManagedIdentity = (
|
||||
updateIdentityFn: UseManagedIdentityUpdaterParams["updateIdentityFn"]
|
||||
updateIdentityFn: UseManagedIdentityUpdaterParams["updateIdentityFn"],
|
||||
): UseManagedIdentityUpdaterReturn => {
|
||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -29,18 +29,14 @@ const useManagedIdentity = (
|
||||
const {
|
||||
subscriptionId: targetSubscriptionId,
|
||||
resourceGroup: targetResourceGroup,
|
||||
accountName: targetAccountName
|
||||
accountName: targetAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
|
||||
|
||||
const updatedAccount = await updateIdentityFn(
|
||||
targetSubscriptionId,
|
||||
targetResourceGroup,
|
||||
targetAccountName
|
||||
);
|
||||
const updatedAccount = await updateIdentityFn(targetSubscriptionId, targetResourceGroup, targetAccountName);
|
||||
if (updatedAccount) {
|
||||
setCopyJobState((prevState) => ({
|
||||
...prevState,
|
||||
target: { ...prevState.target, account: updatedAccount }
|
||||
target: { ...prevState.target, account: updatedAccount },
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
+25
-35
@@ -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";
|
||||
@@ -23,7 +14,7 @@ import PointInTimeRestore from "../PointInTimeRestore";
|
||||
export interface PermissionSectionConfig {
|
||||
id: string;
|
||||
title: string;
|
||||
Component: React.ComponentType
|
||||
Component: React.ComponentType;
|
||||
disabled: boolean;
|
||||
completed?: boolean;
|
||||
validate?: (state: CopyJobContextState) => boolean | Promise<boolean>;
|
||||
@@ -35,7 +26,7 @@ export const SECTION_IDS = {
|
||||
defaultManagedIdentity: "defaultManagedIdentity",
|
||||
readPermissionAssigned: "readPermissionAssigned",
|
||||
pointInTimeRestore: "pointInTimeRestore",
|
||||
onlineCopyEnabled: "onlineCopyEnabled"
|
||||
onlineCopyEnabled: "onlineCopyEnabled",
|
||||
} as const;
|
||||
|
||||
const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||
@@ -50,7 +41,7 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||
targetAccountIdentityType === IdentityType.SystemAssigned ||
|
||||
targetAccountIdentityType === IdentityType.UserAssigned
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: SECTION_IDS.defaultManagedIdentity,
|
||||
@@ -60,7 +51,7 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||
validate: (state: CopyJobContextState) => {
|
||||
const targetAccountDefaultIdentity = (state?.target?.account?.properties?.defaultIdentity ?? "").toLowerCase();
|
||||
return targetAccountDefaultIdentity === DefaultIdentityType.SystemAssignedIdentity;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: SECTION_IDS.readPermissionAssigned,
|
||||
@@ -73,22 +64,20 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||
const {
|
||||
subscriptionId: sourceSubscriptionId,
|
||||
resourceGroup: sourceResourceGroup,
|
||||
accountName: sourceAccountName
|
||||
accountName: sourceAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
|
||||
|
||||
const rolesAssigned = await fetchRoleAssignments(
|
||||
sourceSubscriptionId,
|
||||
sourceResourceGroup,
|
||||
sourceAccountName,
|
||||
principalId
|
||||
principalId,
|
||||
);
|
||||
|
||||
const roleDefinitions = await fetchRoleDefinitions(
|
||||
rolesAssigned ?? []
|
||||
);
|
||||
const roleDefinitions = await fetchRoleDefinitions(rolesAssigned ?? []);
|
||||
return checkTargetHasReaderRoleOnSource(roleDefinitions ?? []);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
|
||||
@@ -100,32 +89,32 @@ const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
|
||||
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,
|
||||
// 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) =>
|
||||
role.name === "00000000-0000-0000-0000-000000000001" ||
|
||||
role.permissions.some(
|
||||
permission =>
|
||||
(permission) =>
|
||||
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata") &&
|
||||
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read")
|
||||
)
|
||||
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,7 +145,9 @@ const usePermissionSections = (state: CopyJobContextState): PermissionSectionCon
|
||||
|
||||
useEffect(() => {
|
||||
const validateSections = async () => {
|
||||
if (isValidatingRef.current) return;
|
||||
if (isValidatingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
isValidatingRef.current = true;
|
||||
const result: PermissionSectionConfig[] = [];
|
||||
@@ -182,8 +173,7 @@ const usePermissionSections = (state: CopyJobContextState): PermissionSectionCon
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Section has no validate method
|
||||
newValidationCache.set(section.id, false);
|
||||
result.push({ ...section, completed: false });
|
||||
@@ -193,13 +183,13 @@ const usePermissionSections = (state: CopyJobContextState): PermissionSectionCon
|
||||
setValidationCache(newValidationCache);
|
||||
setPermissionSections(result);
|
||||
isValidatingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
validateSections();
|
||||
|
||||
return () => {
|
||||
isValidatingRef.current = false;
|
||||
}
|
||||
};
|
||||
}, [state, sectionToValidate]);
|
||||
|
||||
return permissionSections ?? [];
|
||||
|
||||
+1
-2
@@ -4,12 +4,11 @@ const useWindowOpenMonitor = (url: string, onClose?: () => void, intervalMs = 50
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const openWindowAndMonitor = () => {
|
||||
const newWindow = window.open(url, '_blank');
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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,16 +9,12 @@ import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||
import { CopyJobMigrationType } from "../../../Enums";
|
||||
import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
||||
|
||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({
|
||||
id,
|
||||
title,
|
||||
Component,
|
||||
completed,
|
||||
disabled
|
||||
}) => (
|
||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => (
|
||||
<AccordionItem key={id} value={id} disabled={disabled}>
|
||||
<AccordionHeader className="accordionHeader">
|
||||
<Text className="accordionHeaderText" variant="medium">{title}</Text>
|
||||
<Text className="accordionHeaderText" variant="medium">
|
||||
{title}
|
||||
</Text>
|
||||
<Image
|
||||
className="statusIcon"
|
||||
src={completed ? CheckmarkIcon : WarningIcon}
|
||||
@@ -32,7 +23,7 @@ const PermissionSection: React.FC<PermissionSectionConfig> = ({
|
||||
height={completed ? 20 : 24}
|
||||
/>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel aria-disabled={disabled} className="accordionPanel" >
|
||||
<AccordionPanel aria-disabled={disabled} className="accordionPanel">
|
||||
<Component />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
@@ -45,11 +36,11 @@ const AssignPermissions = () => {
|
||||
|
||||
const indentLevels = React.useMemo<IndentLevel[]>(
|
||||
() => Array(copyJobState.migrationType === CopyJobMigrationType.Online ? 5 : 3).fill({ level: 0, width: "100%" }),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const firstIncompleteSection = permissionSections.find(section => !section.completed);
|
||||
const firstIncompleteSection = permissionSections.find((section) => !section.completed);
|
||||
const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : [];
|
||||
if (JSON.stringify(openItems) !== JSON.stringify(nextOpenItems)) {
|
||||
setOpenItems(nextOpenItems);
|
||||
@@ -58,26 +49,16 @@ const AssignPermissions = () => {
|
||||
|
||||
return (
|
||||
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 15 }}>
|
||||
<span>
|
||||
{ContainerCopyMessages.assignPermissions.description}
|
||||
</span>
|
||||
{
|
||||
permissionSections?.length === 0 ? (
|
||||
<ShimmerTree indentLevels={indentLevels} style={{ width: '100%' }} />
|
||||
<span>{ContainerCopyMessages.assignPermissions.description}</span>
|
||||
{permissionSections?.length === 0 ? (
|
||||
<ShimmerTree indentLevels={indentLevels} style={{ width: "100%" }} />
|
||||
) : (
|
||||
<Accordion
|
||||
className="permissionsAccordion"
|
||||
collapsible
|
||||
openItems={openItems}
|
||||
>
|
||||
{
|
||||
permissionSections.map(section => (
|
||||
<Accordion className="permissionsAccordion" collapsible openItems={openItems}>
|
||||
{permissionSections.map((section) => (
|
||||
<PermissionSection key={section.id} {...section} />
|
||||
))
|
||||
}
|
||||
))}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,13 +10,11 @@ interface FieldRowProps {
|
||||
const FieldRow: React.FC<FieldRowProps> = ({ label = "", children, labelClassName = "" }) => {
|
||||
return (
|
||||
<Stack horizontal horizontalAlign="space-between" className="flex-row">
|
||||
{
|
||||
label && (
|
||||
{label && (
|
||||
<Stack.Item align="center" className="flex-fixed-width">
|
||||
<label className={`field-label ${labelClassName}`}>{label}: </label>
|
||||
</Stack.Item>
|
||||
)
|
||||
}
|
||||
)}
|
||||
<Stack.Item align="center" className="flex-grow-col">
|
||||
{children}
|
||||
</Stack.Item>
|
||||
|
||||
@@ -3,8 +3,10 @@ import React from "react";
|
||||
import InfoIcon from "../../../../../../images/Info.svg";
|
||||
|
||||
const InfoTooltip: React.FC<{ content?: string }> = ({ content }) => {
|
||||
if (!content) return null;
|
||||
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: 'inline-block' } };
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: "inline-block" } };
|
||||
return (
|
||||
<TooltipHost content={content} calloutProps={{ gapSpace: 0 }} styles={hostStyles}>
|
||||
<Image src={InfoIcon} alt="Information" width={14} height={14} />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/display-name */
|
||||
import { DefaultButton, PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
@@ -9,10 +11,17 @@ interface PopoverContainerProps {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(({ isLoading = false, title, children, onPrimary, onCancel }) => {
|
||||
const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
|
||||
({ isLoading = false, title, children, onPrimary, onCancel }) => {
|
||||
return (
|
||||
<Stack className={`popover-container foreground ${isLoading ? "loading" : ""}`} tokens={{ childrenGap: 20 }} style={{ maxWidth: 450 }}>
|
||||
<Text variant="mediumPlus" style={{ fontWeight: 600 }}>{title}</Text>
|
||||
<Stack
|
||||
className={`popover-container foreground ${isLoading ? "loading" : ""}`}
|
||||
tokens={{ childrenGap: 20 }}
|
||||
style={{ maxWidth: 450 }}
|
||||
>
|
||||
<Text variant="mediumPlus" style={{ fontWeight: 600 }}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text>{children}</Text>
|
||||
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
||||
<PrimaryButton
|
||||
@@ -25,7 +34,8 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(({ isLoadin
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
interface PopoverMessageProps {
|
||||
isLoading?: boolean;
|
||||
@@ -36,8 +46,17 @@ interface PopoverMessageProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PopoverMessage: React.FC<PopoverMessageProps> = ({ isLoading = false, visible, title, onCancel, onPrimary, children }) => {
|
||||
if (!visible) return null;
|
||||
const PopoverMessage: React.FC<PopoverMessageProps> = ({
|
||||
isLoading = false,
|
||||
visible,
|
||||
title,
|
||||
onCancel,
|
||||
onPrimary,
|
||||
children,
|
||||
}) => {
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PopoverContainer title={title} onCancel={onCancel} onPrimary={onPrimary} isLoading={isLoading}>
|
||||
{children}
|
||||
|
||||
@@ -11,7 +11,7 @@ const CreateCopyJobScreens: React.FC = () => {
|
||||
handlePrimary,
|
||||
handlePrevious,
|
||||
handleCancel,
|
||||
primaryBtnText
|
||||
primaryBtnText,
|
||||
} = useCopyJobNavigation();
|
||||
|
||||
return (
|
||||
|
||||
+19
-19
@@ -6,38 +6,38 @@ const commonProps = {
|
||||
maxWidth: 140,
|
||||
styles: {
|
||||
root: {
|
||||
whiteSpace: 'normal',
|
||||
lineHeight: '1.2',
|
||||
wordBreak: 'break-word'
|
||||
}
|
||||
}
|
||||
whiteSpace: "normal",
|
||||
lineHeight: "1.2",
|
||||
wordBreak: "break-word",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const getPreviewCopyJobDetailsListColumns = function (): IColumn[] {
|
||||
export const getPreviewCopyJobDetailsListColumns = (): IColumn[] => {
|
||||
return [
|
||||
{
|
||||
key: 'sourcedbname',
|
||||
key: "sourcedbname",
|
||||
name: ContainerCopyMessages.sourceDatabaseLabel,
|
||||
fieldName: 'sourceDatabaseName',
|
||||
...commonProps
|
||||
fieldName: "sourceDatabaseName",
|
||||
...commonProps,
|
||||
},
|
||||
{
|
||||
key: 'sourcecolname',
|
||||
key: "sourcecolname",
|
||||
name: ContainerCopyMessages.sourceContainerLabel,
|
||||
fieldName: 'sourceContainerName',
|
||||
...commonProps
|
||||
fieldName: "sourceContainerName",
|
||||
...commonProps,
|
||||
},
|
||||
{
|
||||
key: 'targetdbname',
|
||||
key: "targetdbname",
|
||||
name: ContainerCopyMessages.targetDatabaseLabel,
|
||||
fieldName: 'targetDatabaseName',
|
||||
...commonProps
|
||||
fieldName: "targetDatabaseName",
|
||||
...commonProps,
|
||||
},
|
||||
{
|
||||
key: 'targetcolname',
|
||||
key: "targetcolname",
|
||||
name: ContainerCopyMessages.targetContainerLabel,
|
||||
fieldName: 'targetContainerName',
|
||||
...commonProps
|
||||
}
|
||||
fieldName: "targetContainerName",
|
||||
...commonProps,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
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 selectedDatabaseAndContainers = [{
|
||||
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 || '',
|
||||
jobName: newValue || "",
|
||||
}));
|
||||
};
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer">
|
||||
<FieldRow label={ContainerCopyMessages.jobNameLabel}>
|
||||
<TextField
|
||||
value={jobName}
|
||||
onChange={onJobNameChange}
|
||||
/>
|
||||
<TextField value={jobName} onChange={onJobNameChange} />
|
||||
</FieldRow>
|
||||
<Stack>
|
||||
<Text className='bold'>{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
|
||||
<Text className="bold">{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
|
||||
<Text>{copyJobState.source?.subscription?.displayName}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text className='bold'>{ContainerCopyMessages.sourceAccountLabel}</Text>
|
||||
<Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text>
|
||||
<Text>{copyJobState.source?.account?.name}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
|
||||
+3
-1
@@ -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";
|
||||
@@ -24,5 +26,5 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = React.memo(
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
)
|
||||
),
|
||||
);
|
||||
+5
-9
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/display-name */
|
||||
import { Checkbox, Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
@@ -7,14 +9,8 @@ interface MigrationTypeCheckboxProps {
|
||||
onChange: (_ev?: React.FormEvent, checked?: boolean) => void;
|
||||
}
|
||||
|
||||
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(
|
||||
({ checked, onChange }) => (
|
||||
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => (
|
||||
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow">
|
||||
<Checkbox
|
||||
label={ContainerCopyMessages.migrationTypeCheckboxLabel}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Checkbox label={ContainerCopyMessages.migrationTypeCheckboxLabel} checked={checked} onChange={onChange} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
+3
-1
@@ -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";
|
||||
@@ -22,5 +24,5 @@ export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.m
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
)
|
||||
),
|
||||
);
|
||||
+8
-8
@@ -5,10 +5,10 @@ import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } f
|
||||
|
||||
export function useDropdownOptions(
|
||||
subscriptions: Subscription[],
|
||||
accounts: DatabaseAccount[]
|
||||
accounts: DatabaseAccount[],
|
||||
): {
|
||||
subscriptionOptions: DropdownOptionType[],
|
||||
accountOptions: DropdownOptionType[]
|
||||
subscriptionOptions: DropdownOptionType[];
|
||||
accountOptions: DropdownOptionType[];
|
||||
} {
|
||||
const subscriptionOptions = React.useMemo(
|
||||
() =>
|
||||
@@ -17,7 +17,7 @@ export function useDropdownOptions(
|
||||
text: sub.displayName,
|
||||
data: sub,
|
||||
})) || [],
|
||||
[subscriptions]
|
||||
[subscriptions],
|
||||
);
|
||||
|
||||
const accountOptions = React.useMemo(
|
||||
@@ -27,7 +27,7 @@ export function useDropdownOptions(
|
||||
text: account.name,
|
||||
data: account,
|
||||
})) || [],
|
||||
[accounts]
|
||||
[accounts],
|
||||
);
|
||||
|
||||
return { subscriptionOptions, accountOptions };
|
||||
@@ -37,7 +37,7 @@ type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
|
||||
|
||||
export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
|
||||
const handleSelectSourceAccount = React.useCallback(
|
||||
(type: "subscription" | "account", data: Subscription & DatabaseAccount | undefined) => {
|
||||
(type: "subscription" | "account", data: (Subscription & DatabaseAccount) | undefined) => {
|
||||
setCopyJobState((prevState: CopyJobContextState) => {
|
||||
if (type === "subscription") {
|
||||
return {
|
||||
@@ -61,7 +61,7 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
|
||||
return prevState;
|
||||
});
|
||||
},
|
||||
[setCopyJobState]
|
||||
[setCopyJobState],
|
||||
);
|
||||
|
||||
const handleMigrationTypeChange = React.useCallback(
|
||||
@@ -71,7 +71,7 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
|
||||
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
|
||||
}));
|
||||
},
|
||||
[setCopyJobState]
|
||||
[setCopyJobState],
|
||||
);
|
||||
|
||||
return { handleSelectSourceAccount, handleMigrationTypeChange };
|
||||
|
||||
@@ -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,16 +12,15 @@ import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox";
|
||||
import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
|
||||
import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils";
|
||||
|
||||
interface SelectAccountProps { }
|
||||
|
||||
const SelectAccount = React.memo(
|
||||
(_props: SelectAccountProps) => {
|
||||
const SelectAccount = React.memo(() => {
|
||||
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 sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter(
|
||||
(account) => account.type === "SQL" || account.kind === "GlobalDocumentDB",
|
||||
);
|
||||
|
||||
const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts);
|
||||
const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState);
|
||||
@@ -44,13 +44,9 @@ const SelectAccount = React.memo(
|
||||
onChange={(_ev, option) => handleSelectSourceAccount("account", option?.data)}
|
||||
/>
|
||||
|
||||
<MigrationTypeCheckbox
|
||||
checked={migrationTypeChecked}
|
||||
onChange={handleMigrationTypeChange}
|
||||
/>
|
||||
<MigrationTypeCheckbox checked={migrationTypeChecked} onChange={handleMigrationTypeChange} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export default SelectAccount;
|
||||
|
||||
+8
-9
@@ -1,36 +1,35 @@
|
||||
import React from "react";
|
||||
import { CopyJobContextState, DropdownOptionType } from "../../../../Types";
|
||||
|
||||
export function dropDownChangeHandler(
|
||||
setCopyJobState: React.Dispatch<React.SetStateAction<CopyJobContextState>>
|
||||
) {
|
||||
export function dropDownChangeHandler(setCopyJobState: React.Dispatch<React.SetStateAction<CopyJobContextState>>) {
|
||||
return (type: "sourceDatabase" | "sourceContainer" | "targetDatabase" | "targetContainer") =>
|
||||
(_evnt: any, option: DropdownOptionType) => {
|
||||
(_evnt: React.FormEvent, option: DropdownOptionType) => {
|
||||
const value = option.key;
|
||||
setCopyJobState((prevState) => {
|
||||
switch (type) {
|
||||
case "sourceDatabase":
|
||||
return {
|
||||
...prevState,
|
||||
source: { ...prevState.source, databaseId: value, containerId: undefined }
|
||||
source: { ...prevState.source, databaseId: value, containerId: undefined },
|
||||
};
|
||||
case "sourceContainer":
|
||||
return {
|
||||
...prevState,
|
||||
source: { ...prevState.source, containerId: value }
|
||||
source: { ...prevState.source, containerId: value },
|
||||
};
|
||||
case "targetDatabase":
|
||||
return {
|
||||
...prevState,
|
||||
target: { ...prevState.target, databaseId: value, containerId: undefined }
|
||||
target: { ...prevState.target, databaseId: value, containerId: undefined },
|
||||
};
|
||||
case "targetContainer":
|
||||
return {
|
||||
...prevState,
|
||||
target: { ...prevState.target, containerId: value }
|
||||
target: { ...prevState.target, containerId: value },
|
||||
};
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ export const DatabaseContainerSection = ({
|
||||
containerOptions,
|
||||
selectedContainer,
|
||||
containerDisabled,
|
||||
containerOnChange
|
||||
containerOnChange,
|
||||
}: DatabaseContainerSectionProps) => (
|
||||
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
|
||||
<label className="subHeading">{heading}</label>
|
||||
|
||||
+12
-20
@@ -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,18 +9,10 @@ import { DatabaseContainerSection } from "./components/DatabaseContainerSection"
|
||||
import { dropDownChangeHandler } from "./Events/DropDownChangeHandler";
|
||||
import { useMemoizedSourceAndTargetData } from "./memoizedData";
|
||||
|
||||
interface SelectSourceAndTargetContainersProps { }
|
||||
|
||||
const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainersProps) => {
|
||||
const SelectSourceAndTargetContainers = () => {
|
||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
const {
|
||||
source,
|
||||
target,
|
||||
sourceDbParams,
|
||||
sourceContainerParams,
|
||||
targetDbParams,
|
||||
targetContainerParams
|
||||
} = useMemoizedSourceAndTargetData(copyJobState);
|
||||
const { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams } =
|
||||
useMemoizedSourceAndTargetData(copyJobState);
|
||||
|
||||
// Custom hooks
|
||||
const sourceDatabases = useDatabases(...sourceDbParams) || [];
|
||||
@@ -29,20 +22,20 @@ const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainers
|
||||
|
||||
// Memoize option objects for dropdowns
|
||||
const sourceDatabaseOptions = React.useMemo(
|
||||
() => sourceDatabases.map((db: any) => ({ key: db.name, text: db.name, data: db })),
|
||||
[sourceDatabases]
|
||||
() => sourceDatabases.map((db: DatabaseModel) => ({ 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]
|
||||
() => sourceContainers.map((c: DatabaseModel) => ({ 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]
|
||||
() => targetDatabases.map((db: DatabaseModel) => ({ 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]
|
||||
() => targetContainers.map((c: DatabaseModel) => ({ key: c.name, text: c.name, data: c })),
|
||||
[targetContainers],
|
||||
);
|
||||
|
||||
const onDropdownChange = React.useCallback(dropDownChangeHandler(setCopyJobState), [setCopyJobState]);
|
||||
@@ -76,5 +69,4 @@ const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainers
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SelectSourceAndTargetContainers;
|
||||
+11
-33
@@ -9,56 +9,34 @@ export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState
|
||||
const {
|
||||
subscriptionId: sourceSubscriptionId,
|
||||
resourceGroup: sourceResourceGroup,
|
||||
accountName: sourceAccountName
|
||||
accountName: sourceAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
|
||||
const {
|
||||
subscriptionId: targetSubscriptionId,
|
||||
resourceGroup: targetResourceGroup,
|
||||
accountName: targetAccountName
|
||||
accountName: targetAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
|
||||
|
||||
const sourceDbParams = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
sourceSubscriptionId,
|
||||
sourceResourceGroup,
|
||||
sourceAccountName,
|
||||
'SQL',
|
||||
] as DatabaseParams,
|
||||
[sourceSubscriptionId, sourceResourceGroup, sourceAccountName]
|
||||
() => [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]
|
||||
[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]
|
||||
() => [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]
|
||||
() =>
|
||||
[targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId, "SQL"] as DataContainerParams,
|
||||
[targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId],
|
||||
);
|
||||
|
||||
return { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams };
|
||||
|
||||
@@ -9,10 +9,7 @@ type NavigationState = {
|
||||
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) {
|
||||
@@ -42,13 +39,10 @@ export function useCopyJobNavigation() {
|
||||
const currentScreenKey = state.screenHistory[state.screenHistory.length - 1];
|
||||
const currentScreen = screens.find((screen) => screen.key === currentScreenKey);
|
||||
|
||||
const isPrimaryDisabled = useMemo(
|
||||
() => {
|
||||
const isPrimaryDisabled = useMemo(() => {
|
||||
const context = currentScreenKey === SCREEN_KEYS.AssignPermissions ? cache : copyJobState;
|
||||
return !currentScreen?.validations.every((v) => v.validate(context));
|
||||
},
|
||||
[currentScreen.key, copyJobState, cache]
|
||||
);
|
||||
}, [currentScreen.key, copyJobState, cache]);
|
||||
const primaryBtnText = useMemo(() => {
|
||||
if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) {
|
||||
return "Copy";
|
||||
|
||||
@@ -7,5 +7,5 @@ interface CopyJobPrerequisitesCacheState {
|
||||
|
||||
export const useCopyJobPrerequisitesCache = create<CopyJobPrerequisitesCacheState>((set) => ({
|
||||
validationCache: new Map<string, boolean>(),
|
||||
setValidationCache: (cache) => set({ validationCache: cache })
|
||||
setValidationCache: (cache) => set({ validationCache: cache }),
|
||||
}));
|
||||
@@ -41,9 +41,11 @@ function useCreateCopyJobScreensList() {
|
||||
component: <SelectSourceAndTargetContainers />,
|
||||
validations: [
|
||||
{
|
||||
validate: (state: CopyJobContextState) => (
|
||||
!!state?.source?.databaseId && !!state?.source?.containerId && !!state?.target?.databaseId && !!state?.target?.containerId
|
||||
),
|
||||
validate: (state: CopyJobContextState) =>
|
||||
!!state?.source?.databaseId &&
|
||||
!!state?.source?.containerId &&
|
||||
!!state?.target?.databaseId &&
|
||||
!!state?.target?.containerId,
|
||||
message: "Please select source and target containers to proceed",
|
||||
},
|
||||
],
|
||||
@@ -53,11 +55,8 @@ function useCreateCopyJobScreensList() {
|
||||
component: <PreviewCopyJob />,
|
||||
validations: [
|
||||
{
|
||||
validate: (state: CopyJobContextState) => !!(
|
||||
typeof state?.jobName === "string"
|
||||
&& state?.jobName
|
||||
&& /^[a-zA-Z0-9-.]+$/.test(state?.jobName)
|
||||
),
|
||||
validate: (state: CopyJobContextState) =>
|
||||
!!(typeof state?.jobName === "string" && state?.jobName && /^[a-zA-Z0-9-.]+$/.test(state?.jobName)),
|
||||
message: "Please enter a job name to proceed",
|
||||
},
|
||||
],
|
||||
@@ -69,19 +68,20 @@ function useCreateCopyJobScreensList() {
|
||||
{
|
||||
validate: (cache: Map<string, boolean>) => {
|
||||
const cacheValuesIterator = Array.from(cache.values());
|
||||
if (cacheValuesIterator.length === 0) return false;
|
||||
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 };
|
||||
|
||||
|
||||
@@ -10,60 +10,53 @@ interface CopyJobActionMenuProps {
|
||||
}
|
||||
|
||||
const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ 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)
|
||||
onClick: () => handleClick(job, CopyJobActions.pause),
|
||||
},
|
||||
{
|
||||
key: CopyJobActions.cancel,
|
||||
text: ContainerCopyMessages.MonitorJobs.Actions.cancel,
|
||||
onClick: () => handleClick(job, CopyJobActions.cancel)
|
||||
onClick: () => handleClick(job, CopyJobActions.cancel),
|
||||
},
|
||||
{
|
||||
key: CopyJobActions.resume,
|
||||
text: ContainerCopyMessages.MonitorJobs.Actions.resume,
|
||||
onClick: () => handleClick(job, CopyJobActions.resume)
|
||||
}
|
||||
onClick: () => handleClick(job, CopyJobActions.resume),
|
||||
},
|
||||
];
|
||||
|
||||
if (CopyJobStatusType.Paused === job.Status) {
|
||||
return baseItems.filter(item => item.key !== CopyJobActions.pause);
|
||||
return baseItems.filter((item) => item.key !== CopyJobActions.pause);
|
||||
}
|
||||
|
||||
if (CopyJobStatusType.Pending === job.Status) {
|
||||
return baseItems.filter(item => item.key !== CopyJobActions.resume);
|
||||
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 (
|
||||
[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)
|
||||
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;
|
||||
|
||||
@@ -9,7 +9,7 @@ export const getColumns = (
|
||||
handleSort: (columnKey: string) => void,
|
||||
handleActionClick: (job: CopyJobType, action: string) => void,
|
||||
sortedColumnKey: string | undefined,
|
||||
isSortedDescending: boolean
|
||||
isSortedDescending: boolean,
|
||||
): IColumn[] => [
|
||||
{
|
||||
key: "LastUpdatedTime",
|
||||
@@ -75,4 +75,4 @@ export const getColumns = (
|
||||
isResizable: true,
|
||||
onRender: (job: CopyJobType) => <CopyJobActionMenu job={job} handleClick={handleActionClick} />,
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
@@ -5,21 +5,21 @@ 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
|
||||
@@ -33,18 +33,21 @@ const iconMap: Record<CopyJobStatusType, string> = {
|
||||
[CopyJobStatusType.Cancelled]: "Blocked2Solid",
|
||||
[CopyJobStatusType.Failed]: "AlertSolid",
|
||||
[CopyJobStatusType.Faulted]: "AlertSolid",
|
||||
[CopyJobStatusType.Completed]: "CompletedSolid"
|
||||
[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 (
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<FontIcon
|
||||
aria-label={status}
|
||||
iconName={iconMap[status] || "UnknownSolid"}
|
||||
className={classNames[status] || classNames.unknown}
|
||||
/>
|
||||
<Text>{(ContainerCopyMessages.MonitorJobs.Status as any)[status]}</Text>
|
||||
<Text>{statusText}</Text>
|
||||
</Stack>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyJobStatusWithIcon;
|
||||
@@ -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<CopyJobsNotFoundProps> = () => {
|
||||
|
||||
const handleCreateCopyJob = useCallback(Actions.openCreateCopyJobPanel, []);
|
||||
return (
|
||||
<div className='notFoundContainer flexContainer centerContent'>
|
||||
<div className="notFoundContainer flexContainer centerContent">
|
||||
<Image src={CopyJobIcon} alt={ContainerCopyMessages.noCopyJobsTitle} width={100} height={100} />
|
||||
<h4 className='noCopyJobsMessage'>{ContainerCopyMessages.noCopyJobsTitle}</h4>
|
||||
<ActionButton allowDisabledFocus className='createCopyJobButton' onClick={handleCreateCopyJob}>
|
||||
<h4 className="noCopyJobsMessage">{ContainerCopyMessages.noCopyJobsTitle}</h4>
|
||||
<ActionButton allowDisabledFocus className="createCopyJobButton" onClick={handleCreateCopyJob}>
|
||||
{ContainerCopyMessages.createCopyJobButtonText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CopyJobsNotFound;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
ConstrainMode,
|
||||
DetailsListLayoutMode,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
ShimmeredDetailsList,
|
||||
Stack,
|
||||
Sticky,
|
||||
StickyPositionType
|
||||
StickyPositionType,
|
||||
} from "@fluentui/react";
|
||||
import React, { useEffect } from "react";
|
||||
import { CopyJobType } from "../../Types";
|
||||
@@ -16,20 +17,20 @@ import { getColumns } from "./CopyJobColumns";
|
||||
|
||||
interface CopyJobsListProps {
|
||||
jobs: CopyJobType[];
|
||||
handleActionClick: (job: CopyJobType, action: string) => void,
|
||||
pageSize?: number
|
||||
handleActionClick: (job: CopyJobType, action: string) => void;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
const styles = {
|
||||
container: { height: 'calc(100vh - 15em)' } 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<CopyJobsListProps> = ({ jobs, handleActionClick, pageSize = PAGE_SIZE }) => {
|
||||
const [startIndex, setStartIndex] = React.useState(0);
|
||||
const [sortedJobs, setSortedJobs] = React.useState(jobs);
|
||||
const [startIndex] = React.useState(0);
|
||||
const [sortedJobs, setSortedJobs] = React.useState<CopyJobType[]>(jobs);
|
||||
const [sortedColumnKey, setSortedColumnKey] = React.useState<string | undefined>(undefined);
|
||||
const [isSortedDescending, setIsSortedDescending] = React.useState<boolean>(false);
|
||||
|
||||
@@ -40,21 +41,26 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
|
||||
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;
|
||||
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]
|
||||
[handleSort, handleActionClick, sortedColumnKey, isSortedDescending],
|
||||
);
|
||||
|
||||
const _handleRowClick = React.useCallback((job: CopyJobType) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Row clicked:", job);
|
||||
}, []);
|
||||
|
||||
@@ -71,12 +77,7 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<Stack verticalFill={true}>
|
||||
<Stack.Item
|
||||
verticalFill={true}
|
||||
grow={1}
|
||||
shrink={1}
|
||||
style={styles.stackItem}
|
||||
>
|
||||
<Stack.Item verticalFill={true} grow={1} shrink={1} style={styles.stackItem}>
|
||||
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
|
||||
<ShimmeredDetailsList
|
||||
onRenderRow={_onRenderRow}
|
||||
@@ -97,6 +98,6 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default CopyJobsList;
|
||||
@@ -1,16 +1,17 @@
|
||||
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;
|
||||
@@ -23,15 +24,16 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
|
||||
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<IndentLevel[]>(
|
||||
() => Array(7).fill({ level: 0, width: "100%" }),
|
||||
[]
|
||||
);
|
||||
const indentLevels = React.useMemo<IndentLevel[]>(() => Array(7).fill({ level: 0, width: "100%" }), []);
|
||||
|
||||
const fetchJobs = React.useCallback(async () => {
|
||||
if (isUpdatingRef.current) return; // Skip if an update is in progress
|
||||
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
|
||||
if (isFirstFetchRef.current) {
|
||||
setLoading(true);
|
||||
} // Show loading spinner only for the first fetch
|
||||
setError(null);
|
||||
|
||||
const response = await getCopyJobs();
|
||||
@@ -64,7 +66,7 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
|
||||
return;
|
||||
}
|
||||
fetchJobs();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const handleActionClick = React.useCallback(async (job: CopyJobType, action: string) => {
|
||||
@@ -77,9 +79,10 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
|
||||
prevJob.Name === updatedCopyJob.properties.jobName
|
||||
? {
|
||||
...prevJob,
|
||||
Status: convertToCamelCase(updatedCopyJob.properties.status) as CopyJobStatusType
|
||||
} : prevJob
|
||||
)
|
||||
Status: convertToCamelCase(updatedCopyJob.properties.status) as CopyJobStatusType,
|
||||
}
|
||||
: prevJob,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -90,20 +93,20 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>((_p
|
||||
}, []);
|
||||
|
||||
const memoizedJobsList = React.useMemo(() => {
|
||||
if (loading) return null;
|
||||
if (jobs.length > 0) return <CopyJobsList jobs={jobs} handleActionClick={handleActionClick} />;
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
if (jobs.length > 0) {
|
||||
return <CopyJobsList jobs={jobs} handleActionClick={handleActionClick} />;
|
||||
}
|
||||
return <CopyJobsNotFound />;
|
||||
}, [jobs, loading, handleActionClick]);
|
||||
|
||||
return (
|
||||
<Stack className='monitorCopyJobs flexContainer'>
|
||||
{loading && <ShimmerTree indentLevels={indentLevels} style={{ width: '100%', padding: '1rem 2.5rem' }} />}
|
||||
<Stack className="monitorCopyJobs flexContainer">
|
||||
{loading && <ShimmerTree indentLevels={indentLevels} style={{ width: "100%", padding: "1rem 2.5rem" }} />}
|
||||
{error && (
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.error}
|
||||
isMultiline={false}
|
||||
onDismiss={() => setError(null)}
|
||||
>
|
||||
<MessageBar messageBarType={MessageBarType.error} isMultiline={false} onDismiss={() => setError(null)}>
|
||||
{error}
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
@@ -22,35 +22,31 @@ export type CopyJobTabForwardRefHandle = {
|
||||
};
|
||||
|
||||
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
|
||||
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<HTMLDivElement>, option: DropdownOptionType) => void;
|
||||
containerOptions: DropdownOptionType[];
|
||||
selectedContainer: string;
|
||||
containerDisabled?: boolean;
|
||||
containerOnChange: (ev: React.FormEvent<HTMLDivElement>, option: DropdownOptionType) => void;
|
||||
}
|
||||
|
||||
export interface CopyJobContextState {
|
||||
@@ -63,14 +59,14 @@ export interface CopyJobContextState {
|
||||
account: DatabaseAccount;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
},
|
||||
};
|
||||
// target details
|
||||
target: {
|
||||
subscriptionId: string;
|
||||
account: DatabaseAccount;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface CopyJobFlowType {
|
||||
@@ -94,8 +90,8 @@ export type CopyJobType = {
|
||||
Duration: string;
|
||||
LastUpdatedTime: string;
|
||||
timestamp: number;
|
||||
Error?: CopyJobErrorType
|
||||
}
|
||||
Error?: CopyJobErrorType;
|
||||
};
|
||||
|
||||
export interface CopyJobErrorType {
|
||||
message: string;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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<ContainerCopyProps> = ({ container }) => {
|
||||
const monitorCopyJobsRef = React.useRef<MonitorCopyJobsRef>();
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface PanelContainerProps {
|
||||
panelContent?: JSX.Element;
|
||||
isConsoleExpanded: boolean;
|
||||
isOpen: boolean;
|
||||
hasConsole: boolean
|
||||
hasConsole?: boolean;
|
||||
isConsoleAnimationFinished?: boolean;
|
||||
panelWidth?: string;
|
||||
onRenderNavigationContent?: IRenderFunction<IPanelProps>;
|
||||
|
||||
+5
-7
@@ -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 <LoadingExplorer />;
|
||||
}
|
||||
console.log("Using explorer: ", explorer);
|
||||
console.log("Using userContext: ", userContext);
|
||||
// console.log("Using explorer: ", explorer);
|
||||
// console.log("Using userContext: ", userContext);
|
||||
|
||||
return (
|
||||
<KeyboardShortcutRoot>
|
||||
<div className="flexContainer" aria-hidden="false" data-test="DataExplorerRoot">
|
||||
{
|
||||
userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
|
||||
{userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
|
||||
<ContainerCopyPanel container={explorer} />
|
||||
) : (
|
||||
<DivExplorer explorer={explorer} />
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
<SidePanel />
|
||||
<Dialog />
|
||||
|
||||
@@ -8,6 +8,7 @@ describe("AuthorizationUtils", () => {
|
||||
const setAadDataPlane = (enabled: boolean) => {
|
||||
updateUserContext({
|
||||
features: {
|
||||
enableContainerCopy: false,
|
||||
enableAadDataPlane: enabled,
|
||||
canExceedMaximumValue: false,
|
||||
cosmosdb: false,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { userContext } from "UserContext";
|
||||
|
||||
export function getCopyJobAuthorizationHeader(token: string = ""): Headers {
|
||||
if (!token && !userContext.authorizationToken) {
|
||||
throw new Error("Authorization token is missing");
|
||||
}
|
||||
const headers = new Headers();
|
||||
const authToken = token ? `Bearer ${token}` : userContext.authorizationToken;
|
||||
const authToken = token ? `Bearer ${token}` : userContext.authorizationToken ?? "";
|
||||
headers.append("Authorization", authToken);
|
||||
headers.append("Content-Type", "application/json");
|
||||
return headers;
|
||||
|
||||
+20
-19
@@ -43,15 +43,12 @@ const getArmBaseUrl = (): string => {
|
||||
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 || ""}`
|
||||
);
|
||||
throw new Error(`Failed to fetch ${context}. Status: ${response.status}. ${body || ""}`);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
@@ -60,25 +57,22 @@ export const fetchRoleAssignments = async (
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
principalId: string
|
||||
principalId: string,
|
||||
): Promise<RoleAssignmentType[]> => {
|
||||
const uri = buildArmUrl(
|
||||
`/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlRoleAssignments`
|
||||
`/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");
|
||||
|
||||
return (data.value || []).filter(
|
||||
(assignment: RoleAssignmentType) =>
|
||||
assignment?.properties?.principalId === principalId
|
||||
(assignment: RoleAssignmentType) => assignment?.properties?.principalId === principalId,
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchRoleDefinitions = async (
|
||||
roleAssignments: RoleAssignmentType[]
|
||||
): Promise<RoleDefinitionType[]> => {
|
||||
const roleDefinitionIds = roleAssignments.map(assignment => assignment.properties.roleDefinitionId);
|
||||
export const fetchRoleDefinitions = async (roleAssignments: RoleAssignmentType[]): Promise<RoleDefinitionType[]> => {
|
||||
const roleDefinitionIds = roleAssignments.map((assignment) => assignment.properties.roleDefinitionId);
|
||||
const uniqueRoleDefinitionIds = Array.from(new Set(roleDefinitionIds));
|
||||
|
||||
const headers = getCopyJobAuthorizationHeader();
|
||||
@@ -88,7 +82,7 @@ export const fetchRoleDefinitions = async (
|
||||
const responses = await Promise.all(promises);
|
||||
|
||||
const roleDefinitions = await Promise.all(
|
||||
responses.map((res, i) => handleResponse(res, `role definition ${uniqueRoleDefinitionIds[i]}`))
|
||||
responses.map((res, i) => handleResponse(res, `role definition ${uniqueRoleDefinitionIds[i]}`)),
|
||||
);
|
||||
|
||||
return roleDefinitions;
|
||||
@@ -98,8 +92,11 @@ export const assignRole = async (
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
principalId: string
|
||||
): Promise<RoleAssignmentType> => {
|
||||
principalId: string,
|
||||
): Promise<RoleAssignmentType | null> => {
|
||||
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();
|
||||
@@ -109,11 +106,15 @@ export const assignRole = async (
|
||||
properties: {
|
||||
roleDefinitionId,
|
||||
scope: `${accountScope}/`,
|
||||
principalId
|
||||
}
|
||||
principalId,
|
||||
},
|
||||
};
|
||||
const response: RoleAssignmentType = await armRequest({
|
||||
host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body
|
||||
host: configContext.ARM_ENDPOINT,
|
||||
path,
|
||||
method: "PUT",
|
||||
apiVersion,
|
||||
body,
|
||||
});
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -17,13 +17,12 @@ const buildUrl = (params: FetchAccountDetailsParams): string => {
|
||||
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
|
||||
) {
|
||||
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");
|
||||
|
||||
@@ -83,7 +83,7 @@ export async function listByDatabaseAccount(
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
signal?: AbortSignal
|
||||
signal?: AbortSignal,
|
||||
): Promise<Types.DataTransferJobFeedResults> {
|
||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`;
|
||||
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "GET", apiVersion, signal });
|
||||
|
||||
@@ -46,7 +46,6 @@ export type CosmosMongoVCoreDataTransferDataSourceSink = DataTransferDataSourceS
|
||||
connectionStringKeyVaultUri?: string;
|
||||
};
|
||||
|
||||
|
||||
/* A CosmosDB No Sql API data source/sink */
|
||||
export type CosmosSqlDataTransferDataSourceSink = BaseCosmosDataTransferDataSourceSink & {
|
||||
/* undocumented */
|
||||
@@ -76,7 +75,7 @@ export interface DataTransferJobProperties {
|
||||
/* Job Status */
|
||||
readonly status?: string;
|
||||
/* Processed Count. */
|
||||
readonly processedCount?: number
|
||||
readonly processedCount?: number;
|
||||
/* Total Count. */
|
||||
readonly totalCount?: number;
|
||||
/* Last Updated Time (ISO-8601 format). */
|
||||
@@ -87,7 +86,7 @@ export interface DataTransferJobProperties {
|
||||
readonly error?: unknown;
|
||||
|
||||
/* Total Duration of Job */
|
||||
readonly duration?: string
|
||||
readonly duration?: string;
|
||||
/* Mode of job execution */
|
||||
mode?: "Offline" | "Online";
|
||||
}
|
||||
|
||||
@@ -9,11 +9,15 @@ const updateIdentity = async (
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
body: object
|
||||
): Promise<DatabaseAccount> => {
|
||||
body: object,
|
||||
): Promise<DatabaseAccount | null> => {
|
||||
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
|
||||
host: configContext.ARM_ENDPOINT,
|
||||
path,
|
||||
method: "PATCH",
|
||||
apiVersion,
|
||||
body,
|
||||
});
|
||||
if (response.status === "Succeeded") {
|
||||
const account = await fetchDatabaseAccount(subscriptionId, resourceGroupName, accountName);
|
||||
@@ -25,12 +29,12 @@ const updateIdentity = async (
|
||||
const updateSystemIdentity = async (
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string
|
||||
): Promise<DatabaseAccount> => {
|
||||
accountName: string,
|
||||
): Promise<DatabaseAccount | null> => {
|
||||
const body = {
|
||||
identity: {
|
||||
type: "SystemAssigned"
|
||||
}
|
||||
type: "SystemAssigned",
|
||||
},
|
||||
};
|
||||
const updatedAccount = await updateIdentity(subscriptionId, resourceGroupName, accountName, body);
|
||||
return updatedAccount;
|
||||
@@ -39,18 +43,15 @@ const updateSystemIdentity = async (
|
||||
const updateDefaultIdentity = async (
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string
|
||||
): Promise<DatabaseAccount> => {
|
||||
accountName: string,
|
||||
): Promise<DatabaseAccount | null> => {
|
||||
const body = {
|
||||
properties: {
|
||||
defaultIdentity: "SystemAssignedIdentity"
|
||||
}
|
||||
defaultIdentity: "SystemAssignedIdentity",
|
||||
},
|
||||
};
|
||||
const updatedAccount = await updateIdentity(subscriptionId, resourceGroupName, accountName, body);
|
||||
return updatedAccount;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export { updateDefaultIdentity, updateSystemIdentity };
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export async function armRequestWithoutPolling<T>({
|
||||
method,
|
||||
headers,
|
||||
body: requestBody ? JSON.stringify(requestBody) : undefined,
|
||||
signal
|
||||
signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -119,7 +119,7 @@ export async function armRequest<T>({
|
||||
queryParams,
|
||||
contentType,
|
||||
customHeaders,
|
||||
signal
|
||||
signal,
|
||||
}: Options): Promise<T> {
|
||||
const armRequestResult = await armRequestWithoutPolling<T>({
|
||||
host,
|
||||
@@ -130,7 +130,7 @@ export async function armRequest<T>({
|
||||
queryParams,
|
||||
contentType,
|
||||
customHeaders,
|
||||
signal
|
||||
signal,
|
||||
});
|
||||
const operationStatusUrl = armRequestResult.operationStatusUrl;
|
||||
if (operationStatusUrl) {
|
||||
|
||||
@@ -24,27 +24,27 @@ const buildReadDataContainersListUrl = (params: FetchDataContainersListParams):
|
||||
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
|
||||
apiType: ApiType,
|
||||
): Promise<DatabaseModel[]> => {
|
||||
const uri = buildReadDataContainersListUrl({
|
||||
subscriptionId,
|
||||
resourceGroupName,
|
||||
accountName,
|
||||
databaseName,
|
||||
apiType
|
||||
apiType,
|
||||
});
|
||||
const headers = getCopyJobAuthorizationHeader();
|
||||
|
||||
const response = await fetch(uri, {
|
||||
method: "GET",
|
||||
headers: headers
|
||||
headers: headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -60,22 +60,15 @@ export function useDataContainers(
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
databaseName: string,
|
||||
apiType: ApiType
|
||||
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
|
||||
),
|
||||
() =>
|
||||
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;
|
||||
|
||||
@@ -11,7 +11,10 @@ interface AccountListResult {
|
||||
value: DatabaseAccount[];
|
||||
}
|
||||
|
||||
export async function fetchDatabaseAccounts(subscriptionId: string, accessToken: string = ""): Promise<DatabaseAccount[]> {
|
||||
export async function fetchDatabaseAccounts(
|
||||
subscriptionId: string,
|
||||
accessToken: string = "",
|
||||
): Promise<DatabaseAccount[]> {
|
||||
if (!accessToken && !userContext.authorizationToken) {
|
||||
return [];
|
||||
}
|
||||
|
||||
+15
-16
@@ -22,15 +22,20 @@ const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string =>
|
||||
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<DatabaseModel[]> => {
|
||||
const fetchDatabasesList = async (
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
apiType: ApiType,
|
||||
): Promise<DatabaseModel[]> => {
|
||||
const uri = buildReadDatabasesListUrl({ subscriptionId, resourceGroupName, accountName, apiType });
|
||||
const headers = getCopyJobAuthorizationHeader();
|
||||
|
||||
const response = await fetch(uri, {
|
||||
method: "GET",
|
||||
headers: headers
|
||||
headers: headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -45,21 +50,15 @@ export function useDatabases(
|
||||
subscriptionId: string,
|
||||
resourceGroupName: string,
|
||||
accountName: string,
|
||||
apiType: ApiType
|
||||
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
|
||||
),
|
||||
() =>
|
||||
subscriptionId && resourceGroupName && accountName && apiType
|
||||
? ["fetchDatabasesLinkedToResource", subscriptionId, resourceGroupName, accountName, apiType]
|
||||
: undefined,
|
||||
(_, subscriptionId, resourceGroupName, accountName, apiType) =>
|
||||
fetchDatabasesList(subscriptionId, resourceGroupName, accountName, apiType),
|
||||
);
|
||||
|
||||
return data;
|
||||
|
||||
Reference in New Issue
Block a user