Compare commits

..

3 Commits

Author SHA1 Message Date
Jade Welton
ff6fb32ad1 Enable CI workflow for hotfix and release branch PRs. 2025-11-06 09:37:35 -08:00
Nishtha Ahuja
6b150dbfa0 Revert "Index Advisor Tab on Execute Query (#2177)" (#2244)
This reverts commit abf4b3bd0f.

Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
2025-11-06 20:17:17 +05:30
vchske
bbdf0ce57e Updating Cosmos DB JS SDK to 4.7 (#2243) 2025-11-05 11:18:03 -08:00
25 changed files with 145 additions and 177 deletions

View File

@@ -8,6 +8,8 @@ on:
pull_request:
branches:
- master
- hotfix/**
- release/**
permissions:
id-token: write
contents: read

8
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.5.0",
"@azure/cosmos": "4.7.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2",
@@ -391,9 +391,9 @@
"license": "0BSD"
},
"node_modules/@azure/cosmos": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.5.0.tgz",
"integrity": "sha512-JsTh4twb6FcwP7rJwxQiNZQ/LGtuF6gmciaxY9Rnp6/A325Lhsw/SH4R2ArpT0yCvozbZpweIwdPfUkXVBtp5w==",
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.7.0.tgz",
"integrity": "sha512-a8OV7E41u/ZDaaaDAFdqTTiJ7c82jZc/+ot3XzNCIIilR25NBB+1ixzWQOAgP8SHRUIKfaUl6wAPdTuiG9I66A==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.1.2",

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.5.0",
"@azure/cosmos": "4.7.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2",
@@ -248,4 +248,4 @@
"printWidth": 120,
"endOfLine": "auto"
}
}
}

View File

@@ -7,6 +7,7 @@ const backendEndpoint = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
const previewSiteEndpoint = "https://dataexplorer-preview.azurewebsites.net";
const previewStorageWebsiteEndpoint = "https://dataexplorerpreview.z5.web.core.windows.net/";
const githubApiUrl = "https://api.github.com/repos/Azure/cosmos-explorer";
const githubPullRequestUrl = "https://github.com/Azure/cosmos-explorer/pull";
const azurePortalMpacEndpoint = "https://ms.portal.azure.com/";
const api = createProxyMiddleware({
@@ -56,7 +57,11 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
fetch(`${githubApiUrl}/pulls/${pr}`)
.then((response) => response.json())
.then(({ head: { sha } }) => {
.then(({ head: { ref, sha } }) => {
const prUrl = new URL(`${githubPullRequestUrl}/${pr}`);
prUrl.hash = ref;
search.set("feature.pr", prUrl.href);
const explorer = new URL(`${previewSiteEndpoint}/commit/${sha}/explorer.html`);
explorer.search = search.toString();

View File

@@ -93,7 +93,7 @@ export class CapabilityNames {
public static readonly EnableDataMasking: string = "EnableDataMasking";
public static readonly EnableDynamicDataMasking: string = "EnableDynamicDataMasking";
public static readonly EnableNoSQLFullTextSearchPreviewFeatures: string = "EnableNoSQLFullTextSearchPreviewFeatures";
public static readonly EnableOnlineCopyFeature: string = "EnableOnlineContainerCopy";
public static readonly EnableOnlineCopyFeature: string = "EnableOnlineCopyFeature";
}
export enum CapacityMode {

View File

@@ -23,10 +23,7 @@ export const handleError = (error: string | ARMError | Error, area: string, cons
};
export const getErrorMessage = (error: string | Error = ""): string => {
let errorMessage = typeof error === "string" ? error : error.message;
if (!errorMessage) {
errorMessage = JSON.stringify(error);
}
const errorMessage = typeof error === "string" ? error : error.message;
return replaceKnownError(errorMessage);
};

View File

@@ -1,6 +1,5 @@
import React from "react";
import { userContext } from "UserContext";
import { logError } from "../../../Common/Logger";
import { useSidePanel } from "../../../hooks/useSidePanel";
import {
cancel,
@@ -160,8 +159,7 @@ export const submitCreateCopyJob = async (state: CopyJobContextState, onSuccess:
onSuccess();
return response;
} catch (error) {
const errorMessage = error.message || "Error submitting create copy job. Please try again later.";
logError(errorMessage, "CopyJob/CopyJobActions.submitCreateCopyJob");
console.error("Error submitting create copy job:", error);
throw error;
}
};
@@ -200,7 +198,8 @@ export const updateCopyJobStatus = async (job: CopyJobType, action: string): Pro
pattern,
`'${ContainerCopyMessages.MonitorJobs.Status.InProgress}'`,
);
logError(`Error updating copy job status: ${normalizedErrorMessage}`, "CopyJob/CopyJobActions.updateCopyJobStatus");
console.error(`Error updating copy job status: ${normalizedErrorMessage}`);
throw error;
}
};

View File

@@ -48,10 +48,8 @@ export default {
// Assign Permissions Screen
assignPermissions: {
crossAccountDescription:
description:
"To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.",
intraAccountOnlineDescription: (accountName: string) =>
`Follow the steps below to enable online copy on your "${accountName}" account.`,
},
toggleBtn: {
onText: "On",
@@ -117,7 +115,7 @@ export default {
},
onlineCopyEnabled: {
title: "Online copy enabled",
description: (accountName: string) => `Enable Online copy on "${accountName}".`,
description: (accountName: string) => `Use Azure CLI to enable Online copy on "${accountName}".`,
hrefText: "Learn more about online copy jobs",
href: "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
buttonText: "Enable Online Copy",

View File

@@ -39,23 +39,16 @@ const getInitialCopyJobState = (): CopyJobContextState => {
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
const [copyJobState, setCopyJobState] = React.useState<CopyJobContextState>(getInitialCopyJobState());
const [flow, setFlow] = React.useState<CopyJobFlowType | null>(null);
const [contextError, setContextError] = React.useState<string | null>(null);
const resetCopyJobState = () => {
setCopyJobState(getInitialCopyJobState());
};
const contextValue: CopyJobContextProviderType = {
contextError,
setContextError,
copyJobState,
setCopyJobState,
flow,
setFlow,
resetCopyJobState,
};
return <CopyJobContext.Provider value={contextValue}>{props.children}</CopyJobContext.Provider>;
return (
<CopyJobContext.Provider value={{ copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
{props.children}
</CopyJobContext.Provider>
);
};
export default CopyJobContextProvider;

View File

@@ -106,7 +106,7 @@ export function getAccountDetailsFromResourceId(accountId: string | undefined) {
return null;
}
const pattern = new RegExp(
"/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\\.DocumentDB?/databaseAccounts/([^/]+)",
"/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\\.DocumentDB/databaseAccounts/([^/]+)",
"i",
);
const matches = accountId.match(pattern);
@@ -114,13 +114,3 @@ export function getAccountDetailsFromResourceId(accountId: string | undefined) {
const [_, subscriptionId, resourceGroup, accountName] = matches || [];
return { subscriptionId, resourceGroup, accountName };
}
export function isIntraAccountCopy(sourceAccountId: string | undefined, targetAccountId: string | undefined): boolean {
const sourceAccountDetails = getAccountDetailsFromResourceId(sourceAccountId);
const targetAccountDetails = getAccountDetailsFromResourceId(targetAccountId);
return (
sourceAccountDetails?.subscriptionId === targetAccountDetails?.subscriptionId &&
sourceAccountDetails?.resourceGroup === targetAccountDetails?.resourceGroup &&
sourceAccountDetails?.accountName === targetAccountDetails?.accountName
);
}

View File

@@ -1,6 +1,5 @@
import { Link, Stack, Text, Toggle } from "@fluentui/react";
import React, { useCallback } from "react";
import { logError } from "../../../../../Common/Logger";
import { assignRole } from "../../../../../Utils/arm/RbacUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
@@ -22,7 +21,7 @@ type AddReadPermissionToDefaultIdentityProps = Partial<PermissionSectionConfig>;
const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIdentityProps> = () => {
const [loading, setLoading] = React.useState(false);
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
const { copyJobState, setCopyJobState } = useCopyJobContext();
const [readPermissionAssigned, onToggle] = useToggle(false);
const handleAddReadPermission = useCallback(async () => {
@@ -49,14 +48,11 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
}));
}
} catch (error) {
const errorMessage =
error.message || "Error assigning read permission to default identity. Please try again later.";
logError(errorMessage, "CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission");
setContextError(errorMessage);
console.error("Error assigning read permission to default identity:", error);
} finally {
setLoading(false);
}
}, [copyJobState, setCopyJobState, setContextError]);
}, [copyJobState, setCopyJobState]);
return (
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>

View File

@@ -6,7 +6,6 @@ import WarningIcon from "../../../../../../images/warning.svg";
import ShimmerTree, { IndentLevel } from "../../../../../Common/ShimmerTree/ShimmerTree";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { isIntraAccountCopy } from "../../../CopyJobUtils";
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
@@ -40,8 +39,6 @@ const AssignPermissions = () => {
[],
);
const isSameAccount = isIntraAccountCopy(copyJobState?.source?.account?.id, copyJobState?.target?.account?.id);
useEffect(() => {
const firstIncompleteSection = permissionSections.find((section) => !section.completed);
const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : [];
@@ -52,13 +49,7 @@ const AssignPermissions = () => {
return (
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 15 }}>
<span>
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
copyJobState?.source?.account?.name || "",
)
: ContainerCopyMessages.assignPermissions.crossAccountDescription}
</span>
<span>{ContainerCopyMessages.assignPermissions.description}</span>
{permissionSections?.length === 0 ? (
<ShimmerTree indentLevels={indentLevels} style={{ width: "100%" }} />
) : (

View File

@@ -1,10 +1,7 @@
import { Link, PrimaryButton, Stack } from "@fluentui/react";
import { CapabilityNames } from "Common/Constants";
import { DatabaseAccount } from "Contracts/DataModels";
import React from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import { logError } from "../../../../../Common/Logger";
import { update as updateDatabaseAccount } from "../../../../../Utils/arm/generatedClients/cosmos/databaseAccounts";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
@@ -22,10 +19,8 @@ const OnlineCopyEnabled: React.FC = () => {
const [showRefreshButton, setShowRefreshButton] = React.useState(false);
const intervalRef = React.useRef<NodeJS.Timeout | null>(null);
const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
const { setContextError, copyJobState: { source } = {}, setCopyJobState } = useCopyJobContext();
const { copyJobState: { source } = {}, setCopyJobState } = useCopyJobContext();
const selectedSourceAccount = source?.account;
const sourceAccountCapabilities = selectedSourceAccount?.properties?.capabilities ?? [];
const {
subscriptionId: sourceSubscriptionId,
resourceGroup: sourceResourceGroup,
@@ -43,24 +38,16 @@ const OnlineCopyEnabled: React.FC = () => {
setLoading(false);
}
} catch (error) {
const errorMessage =
error.message || "Error fetching source account after enabling online copy. Please try again later.";
logError(errorMessage, "CopyJob/OnlineCopyEnabled.handleFetchAccount");
setContextError(errorMessage);
clearAccountFetchInterval();
console.error("Error fetching source account after enabling online copy:", error);
setLoading(false);
}
};
const clearAccountFetchInterval = () => {
const clearIntervalAndShowRefresh = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setLoading(false);
};
const clearIntervalAndShowRefresh = () => {
clearAccountFetchInterval();
setShowRefreshButton(true);
};
@@ -69,42 +56,18 @@ const OnlineCopyEnabled: React.FC = () => {
handleFetchAccount();
};
const handleOnlineCopyEnable = async () => {
setLoading(true);
setShowRefreshButton(false);
try {
await updateDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName, {
properties: {
enableAllVersionsAndDeletesChangeFeed: true,
},
});
await updateDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName, {
properties: {
capabilities: [...sourceAccountCapabilities, { name: CapabilityNames.EnableOnlineCopyFeature }],
},
});
intervalRef.current = setInterval(() => {
handleFetchAccount();
}, 30 * 1000);
timeoutRef.current = setTimeout(
() => {
clearIntervalAndShowRefresh();
},
10 * 60 * 1000,
);
} catch (error) {
const errorMessage = error.message || "Failed to enable online copy feature. Please try again later.";
logError(errorMessage, "CopyJob/OnlineCopyEnabled.handleOnlineCopyEnable");
setContextError(errorMessage);
setLoading(false);
}
};
React.useEffect(() => {
intervalRef.current = setInterval(() => {
handleFetchAccount();
}, 30 * 1000);
timeoutRef.current = setTimeout(
() => {
clearIntervalAndShowRefresh();
},
15 * 60 * 1000,
);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
@@ -126,7 +89,32 @@ const OnlineCopyEnabled: React.FC = () => {
</Link>
</Stack.Item>
<Stack.Item>
{showRefreshButton ? (
<pre style={{ backgroundColor: "#f5f5f5", padding: "10px", borderRadius: "4px", overflow: "auto" }}>
<code>
{`# Set shell variables
$resourceGroupName = <azure_resource_group>
$accountName = <azure_cosmos_db_account_name>
$EnableOnlineContainerCopy = "EnableOnlineContainerCopy"
# List down existing capabilities of your account
$cosmosdb = az cosmosdb show --resource-group $resourceGroupName --name $accountName
$capabilities = (($cosmosdb | ConvertFrom-Json).capabilities)
# Append EnableOnlineContainerCopy capability in the list of capabilities
$capabilitiesToAdd = @()
foreach ($item in $capabilities) {
$capabilitiesToAdd += $item.name
}
$capabilitiesToAdd += $EnableOnlineContainerCopy
# Update Cosmos DB account
az cosmosdb update --capabilities $capabilitiesToAdd -n $accountName -g $resourceGroupName`}
</code>
</pre>
</Stack.Item>
{showRefreshButton && (
<Stack.Item>
<PrimaryButton
className="fullWidth"
text={ContainerCopyMessages.refreshButtonLabel}
@@ -134,16 +122,8 @@ const OnlineCopyEnabled: React.FC = () => {
onClick={handleRefresh}
disabled={loading}
/>
) : (
<PrimaryButton
className="fullWidth"
text={loading ? "" : ContainerCopyMessages.onlineCopyEnabled.buttonText}
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
disabled={loading}
onClick={handleOnlineCopyEnable}
/>
)}
</Stack.Item>
</Stack.Item>
)}
</Stack>
);
};

View File

@@ -2,7 +2,6 @@ import { Link, PrimaryButton, Stack, Text } from "@fluentui/react";
import { DatabaseAccount } from "Contracts/DataModels";
import React, { useEffect, useRef, useState } from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import { logError } from "../../../../../Common/Logger";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { buildResourceLink, getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
@@ -64,23 +63,17 @@ const PointInTimeRestore: React.FC = () => {
setLoading(false);
}
} catch (error) {
const errorMessage =
error.message || "Error fetching source account after Point-in-Time Restore. Please try again later.";
logError(errorMessage, "CopyJob/PointInTimeRestore.handleFetchAccount");
clearAccountFetchInterval();
console.error("Error fetching source account after Point-in-Time Restore:", error);
setLoading(false);
}
};
const clearAccountFetchInterval = () => {
const clearIntervalAndShowRefresh = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setLoading(false);
};
const clearIntervalAndShowRefresh = () => {
clearAccountFetchInterval();
setShowRefreshButton(true);
};
@@ -102,7 +95,7 @@ const PointInTimeRestore: React.FC = () => {
() => {
clearIntervalAndShowRefresh();
},
10 * 60 * 1000,
15 * 60 * 1000,
);
};

View File

@@ -1,6 +1,5 @@
import { DatabaseAccount } from "Contracts/DataModels";
import { useCallback, useState } from "react";
import { logError } from "../../../../../../Common/Logger";
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
import { getAccountDetailsFromResourceId } from "../../../../CopyJobUtils";
@@ -20,7 +19,7 @@ interface UseManagedIdentityUpdaterReturn {
const useManagedIdentity = (
updateIdentityFn: UseManagedIdentityUpdaterParams["updateIdentityFn"],
): UseManagedIdentityUpdaterReturn => {
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
const { copyJobState, setCopyJobState } = useCopyJobContext();
const [loading, setLoading] = useState<boolean>(false);
const handleAddSystemIdentity = useCallback(async (): Promise<void> => {
@@ -41,9 +40,7 @@ const useManagedIdentity = (
}));
}
} catch (error) {
const errorMessage = error.message || "Error enabling system-assigned managed identity. Please try again later.";
logError(errorMessage, "CopyJob/useManagedIdentity.handleAddSystemIdentity");
setContextError(errorMessage);
console.error("Error enabling system-assigned managed identity:", error);
} finally {
setLoading(false);
}

View File

@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { CapabilityNames } from "../../../../../../Common/Constants";
import { fetchRoleAssignments, fetchRoleDefinitions, RoleDefinitionType } from "../../../../../../Utils/arm/RbacUtils";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { getAccountDetailsFromResourceId, isIntraAccountCopy } from "../../../../CopyJobUtils";
import { getAccountDetailsFromResourceId } from "../../../../CopyJobUtils";
import {
BackupPolicyType,
CopyJobMigrationType,
@@ -139,9 +139,7 @@ const usePermissionSections = (state: CopyJobContextState): PermissionSectionCon
const isValidatingRef = useRef(false);
const sectionToValidate = useMemo(() => {
const isSameAccount = isIntraAccountCopy(sourceAccountId, targetAccountId);
const baseSections = isSameAccount ? [] : [...PERMISSION_SECTIONS_CONFIG];
const baseSections = sourceAccountId === targetAccountId ? [] : [...PERMISSION_SECTIONS_CONFIG];
if (state.migrationType === CopyJobMigrationType.Online) {
return [...baseSections, ...PERMISSION_SECTIONS_FOR_ONLINE_JOBS];
}

View File

@@ -1,6 +1,5 @@
import { MessageBar, MessageBarType, Stack } from "@fluentui/react";
import React from "react";
import { useCopyJobContext } from "../../Context/CopyJobContext";
import { useCopyJobNavigation } from "../Utils/useCopyJobNavigation";
import NavigationControls from "./Components/NavigationControls";
@@ -13,23 +12,24 @@ const CreateCopyJobScreens: React.FC = () => {
handlePrevious,
handleCancel,
primaryBtnText,
error,
setError,
} = useCopyJobNavigation();
const { contextError, setContextError } = useCopyJobContext();
return (
<Stack verticalAlign="space-between" className="createCopyJobScreensContainer">
<Stack.Item className="createCopyJobScreensContent">
{contextError && (
{error && (
<MessageBar
className="createCopyJobErrorMessageBar"
messageBarType={MessageBarType.blocked}
isMultiline={false}
onDismiss={() => setContextError(null)}
onDismiss={() => setError(null)}
dismissButtonAriaLabel="Close"
truncated={true}
overflowButtonAriaLabel="See more"
>
{contextError}
{error}
</MessageBar>
)}
{currentScreen?.component}

View File

@@ -2,7 +2,6 @@ import React from "react";
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } from "../../../../Types/CopyJobTypes";
import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache";
export function useDropdownOptions(
subscriptions: Subscription[],
@@ -37,7 +36,6 @@ export function useDropdownOptions(
type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
const { setValidationCache } = useCopyJobPrerequisitesCache();
const handleSelectSourceAccount = React.useCallback(
(type: "subscription" | "account", data: (Subscription & DatabaseAccount) | undefined) => {
setCopyJobState((prevState: CopyJobContextState) => {
@@ -62,9 +60,8 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
}
return prevState;
});
setValidationCache(new Map<string, boolean>());
},
[setCopyJobState, setValidationCache],
[setCopyJobState],
);
const handleMigrationTypeChange = React.useCallback(
@@ -73,9 +70,8 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
...prevState,
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
}));
setValidationCache(new Map<string, boolean>());
},
[setCopyJobState, setValidationCache],
[setCopyJobState],
);
return { handleSelectSourceAccount, handleMigrationTypeChange };

View File

@@ -2,7 +2,6 @@ import { useCallback, useMemo, useReducer, useState } from "react";
import { useSidePanel } from "../../../../hooks/useSidePanel";
import { submitCreateCopyJob } from "../../Actions/CopyJobActions";
import { useCopyJobContext } from "../../Context/CopyJobContext";
import { isIntraAccountCopy } from "../../CopyJobUtils";
import { CopyJobMigrationType } from "../../Enums/CopyJobEnums";
import { useCopyJobPrerequisitesCache } from "./useCopyJobPrerequisitesCache";
import { SCREEN_KEYS, useCreateCopyJobScreensList } from "./useCreateCopyJobScreensList";
@@ -34,7 +33,8 @@ function navigationReducer(state: NavigationState, action: Action): NavigationSt
export function useCopyJobNavigation() {
const [isLoading, setIsLoading] = useState(false);
const { copyJobState, resetCopyJobState, setContextError } = useCopyJobContext();
const [error, setError] = useState<string | null>(null);
const { copyJobState, resetCopyJobState } = useCopyJobContext();
const screens = useCreateCopyJobScreensList();
const { validationCache: cache } = useCopyJobPrerequisitesCache();
const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] });
@@ -71,13 +71,18 @@ export function useCopyJobNavigation() {
containerId: container?.containerId || "",
});
const isSameAccount = (
sourceIds: ReturnType<typeof getContainerIdentifiers>,
targetIds: ReturnType<typeof getContainerIdentifiers>,
) => sourceIds.accountId === targetIds.accountId;
const areContainersIdentical = () => {
const { source, target } = copyJobState;
const sourceIds = getContainerIdentifiers(source);
const targetIds = getContainerIdentifiers(target);
return (
isIntraAccountCopy(sourceIds.accountId, targetIds.accountId) &&
isSameAccount(sourceIds, targetIds) &&
sourceIds.databaseId === targetIds.databaseId &&
sourceIds.containerId === targetIds.containerId
);
@@ -85,10 +90,9 @@ export function useCopyJobNavigation() {
const shouldNotShowPermissionScreen = () => {
const { source, target, migrationType } = copyJobState;
const sourceIds = getContainerIdentifiers(source);
const targetIds = getContainerIdentifiers(target);
return (
migrationType === CopyJobMigrationType.Offline && isIntraAccountCopy(sourceIds.accountId, targetIds.accountId)
migrationType === CopyJobMigrationType.Offline &&
isSameAccount(getContainerIdentifiers(source), getContainerIdentifiers(target))
);
};
@@ -101,7 +105,7 @@ export function useCopyJobNavigation() {
error instanceof Error
? error.message || "Failed to create copy job. Please try again later."
: "Failed to create copy job. Please try again later.";
setContextError(errorMessage);
setError(errorMessage);
} finally {
setIsLoading(false);
}
@@ -109,13 +113,11 @@ export function useCopyJobNavigation() {
const handlePrimary = useCallback(() => {
if (currentScreenKey === SCREEN_KEYS.SelectSourceAndTargetContainers && areContainersIdentical()) {
setContextError(
"Source and destination containers cannot be the same. Please select different containers to proceed.",
);
setError("Source and destination containers cannot be the same. Please select different containers to proceed.");
return;
}
setContextError(null);
setError(null);
const transitions = {
[SCREEN_KEYS.SelectAccount]: shouldNotShowPermissionScreen()
? SCREEN_KEYS.SelectSourceAndTargetContainers
@@ -144,5 +146,7 @@ export function useCopyJobNavigation() {
handlePrevious,
handleCancel,
primaryBtnText,
error,
setError,
};
}

View File

@@ -11,14 +11,7 @@ interface CopyJobActionMenuProps {
const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick }) => {
const [updatingJobAction, setUpdatingJobAction] = React.useState<{ jobName: string; action: string } | null>(null);
if (
[
CopyJobStatusType.Completed,
CopyJobStatusType.Cancelled,
CopyJobStatusType.Failed,
CopyJobStatusType.Faulted,
].includes(job.Status)
) {
if ([CopyJobStatusType.Completed, CopyJobStatusType.Cancelled].includes(job.Status)) {
return null;
}
@@ -62,7 +55,7 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
[CopyJobStatusType.InProgress, CopyJobStatusType.Running, CopyJobStatusType.Partitioning].includes(job.Status)
) {
const filteredItems = baseItems.filter((item) => item.key !== CopyJobActions.resume);
if ((job.Mode ?? "").toLowerCase() === CopyJobMigrationType.Online) {
if (job.Mode === CopyJobMigrationType.Online) {
filteredItems.push({
key: CopyJobActions.complete,
text: ContainerCopyMessages.MonitorJobs.Actions.complete,
@@ -74,7 +67,7 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
return filteredItems;
}
if ([CopyJobStatusType.Skipped].includes(job.Status)) {
if ([CopyJobStatusType.Failed, CopyJobStatusType.Faulted, CopyJobStatusType.Skipped].includes(job.Status)) {
return baseItems.filter((item) => item.key === CopyJobActions.resume);
}

View File

@@ -73,8 +73,6 @@ export interface CopyJobFlowType {
}
export interface CopyJobContextProviderType {
contextError: string | null;
setContextError: React.Dispatch<React.SetStateAction<string | null>>;
flow: CopyJobFlowType;
setFlow: React.Dispatch<React.SetStateAction<CopyJobFlowType>>;
copyJobState: CopyJobContextState | null;

View File

@@ -16,6 +16,7 @@ import InfoIcon from "../../../../images/info_color.svg";
import LoadingIcon from "../../../../images/loading.svg";
import WarningIcon from "../../../../images/warning.svg";
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
import { userContext } from "../../../UserContext";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { ConsoleData, ConsoleDataType } from "./ConsoleData";
@@ -126,6 +127,7 @@ export class NotificationConsoleComponent extends React.Component<
<span className="numWarningItems">{numWarningItems}</span>
</span>
</span>
{userContext.features.pr && <PrPreview pr={userContext.features.pr} />}
<span className="consoleSplitter" />
<span className="headerStatus">
<span className="headerStatusEllipsis" aria-live="assertive" aria-atomic="true">
@@ -291,6 +293,21 @@ export class NotificationConsoleComponent extends React.Component<
};
}
const PrPreview = (props: { pr: string }) => {
const url = new URL(props.pr);
const [, ref] = url.hash.split("#");
url.hash = "";
return (
<>
<span className="consoleSplitter" />
<a target="_blank" rel="noreferrer" href={url.href} style={{ marginRight: "1em", fontWeight: "bold" }}>
{ref}
</a>
</>
);
};
export const NotificationConsole: React.FC = () => {
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
const isExpanded = useNotificationConsole((state) => state.isExpanded);

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
<title>Notebook Viewer</title>
<link rel="shortcut icon" href="../../images/CosmosDB_rgb_ui_lighttheme.ico" type="image/x-icon" />
</head>
<body>
<div class="notebookComponentContainer" id="notebookContent"></div>
</body>
</html>

View File

@@ -25,6 +25,7 @@ export type Features = {
readonly notebookServerUrl?: string;
readonly sandboxNotebookOutputs: boolean;
readonly selfServeType?: string;
readonly pr?: string;
readonly showMinRUSurvey: boolean;
readonly ttl90Days: boolean;
readonly mongoProxyEndpoint?: string;
@@ -95,6 +96,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
notebookServerUrl: get("notebookserverurl"),
sandboxNotebookOutputs: "true" === get("sandboxnotebookoutputs", "true"),
selfServeType: get("selfservetype"),
pr: get("pr"),
showMinRUSurvey: "true" === get("showminrusurvey"),
ttl90Days: "true" === get("ttl90days"),
autoscaleDefault: "true" === get("autoscaledefault"),

View File

@@ -113,6 +113,7 @@ module.exports = function (_env = {}, argv = {}) {
hostedExplorer: "./src/HostedExplorer.tsx",
terminal: "./src/Terminal/index.ts",
cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx",
notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx",
galleryViewer: "./src/GalleryViewer/GalleryViewer.tsx",
selfServe: "./src/SelfServe/SelfServe.tsx",
connectToGitHub: "./src/GitHub/GitHubConnector.ts",
@@ -150,6 +151,11 @@ module.exports = function (_env = {}, argv = {}) {
template: "src/CellOutputViewer/cellOutputViewer.html",
chunks: ["cellOutputViewer"],
}),
new HtmlWebpackPlugin({
filename: "notebookViewer.html",
template: "src/NotebookViewer/notebookViewer.html",
chunks: ["notebookViewer"],
}),
new HtmlWebpackPlugin({
filename: "gallery.html",
template: "src/GalleryViewer/galleryViewer.html",