diff --git a/src/Contracts/DataExplorerMessagesContract.ts b/src/Contracts/DataExplorerMessagesContract.ts index de81ea2ea..405f4e5d5 100644 --- a/src/Contracts/DataExplorerMessagesContract.ts +++ b/src/Contracts/DataExplorerMessagesContract.ts @@ -46,6 +46,10 @@ export type DataExploreMessageV3 = params: { updateType: "created" | "deleted" | "settings"; }; + } + | { + type: FabricMessageTypes.RestoreContainer; + params: []; }; export interface GetCosmosTokenMessageOptions { verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx index abb614042..a0d6035a4 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx @@ -516,7 +516,7 @@ describe("CopyJobActionMenu", () => { expect(screen.getByText("Cancel")).toBeInTheDocument(); }); - it("should handle complete action disabled state for online jobs", () => { + it("should disable complete action when job is being updated", () => { const job = createMockJob({ Status: CopyJobStatusType.InProgress, Mode: CopyJobMigrationType.Online, @@ -530,8 +530,34 @@ describe("CopyJobActionMenu", () => { const completeButton = screen.getByText("Complete"); fireEvent.click(completeButton); + // Simulate dialog confirmation to trigger state update + const [, , , onOkCallback] = mockShowOkCancelModalDialog.mock.calls[0]; + onOkCallback(); + fireEvent.click(actionButton); - expect(screen.getByText("Complete")).toBeInTheDocument(); + const completeButtonAfterClick = screen.getByText("Complete").closest("button"); + expect(completeButtonAfterClick).toBeInTheDocument(); + expect(completeButtonAfterClick).toHaveAttribute("aria-disabled", "true"); + }); + + it("should disable complete action when any other action is being performed", () => { + const job = createMockJob({ + Status: CopyJobStatusType.InProgress, + Mode: CopyJobMigrationType.Online, + }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const pauseButton = screen.getByText("Pause"); + fireEvent.click(pauseButton); + fireEvent.click(actionButton); + + const completeButtonAfterClick = screen.getByText("Complete").closest("button"); + expect(completeButtonAfterClick).toBeInTheDocument(); + expect(completeButtonAfterClick).toHaveAttribute("aria-disabled", "true"); }); }); diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx index 058a717bb..682e20c9a 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx @@ -61,7 +61,6 @@ const CopyJobActionMenu: React.FC = ({ job, handleClick const getMenuItems = (): IContextualMenuProps["items"] => { const isThisJobUpdating = updatingJobAction?.jobName === job.Name; - const updatingAction = updatingJobAction?.action; const baseItems = [ { @@ -105,7 +104,7 @@ const CopyJobActionMenu: React.FC = ({ job, handleClick text: ContainerCopyMessages.MonitorJobs.Actions.complete, iconProps: { iconName: "CheckMark" }, onClick: () => showActionConfirmationDialog(job, CopyJobActions.complete), - disabled: isThisJobUpdating && updatingAction === CopyJobActions.complete, + disabled: isThisJobUpdating, }); } return filteredItems; diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index 3f6a795ee..76b75dda8 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -7,7 +7,7 @@ import { AddGlobalSecondaryIndexPanelProps, } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/AddGlobalSecondaryIndexPanel"; import { useDatabases } from "Explorer/useDatabases"; -import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil"; +import { isFabric, isFabricNative, openRestoreContainerDialog } from "Platform/Fabric/FabricUtil"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import { traceOpen } from "Shared/Telemetry/TelemetryProcessor"; import { ReactTabKind, useTabs } from "hooks/useTabs"; @@ -35,6 +35,7 @@ import StoredProcedure from "./Tree/StoredProcedure"; import Trigger from "./Tree/Trigger"; import UserDefinedFunction from "./Tree/UserDefinedFunction"; import { useSelectedNode } from "./useSelectedNode"; +import { extractFeatures } from "../Platform/Hosted/extractFeatures"; export interface CollectionContextMenuButtonParams { databaseId: string; @@ -60,6 +61,17 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin }, ]; + if (isFabricNative() && !userContext.fabricContext?.isReadOnly) { + const features = extractFeatures(); + if (features?.enableRestoreContainer) { + items.push({ + iconSrc: AddCollectionIcon, + onClick: () => openRestoreContainerDialog(), + label: `Restore ${getCollectionName()}`, + }); + } + } + if (!isFabricNative() && (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations)) { items.push({ iconSrc: DeleteDatabaseIcon, diff --git a/src/Explorer/SplashScreen/SplashScreen.less b/src/Explorer/SplashScreen/SplashScreen.less index b9474c6c3..4c1cefe77 100644 --- a/src/Explorer/SplashScreen/SplashScreen.less +++ b/src/Explorer/SplashScreen/SplashScreen.less @@ -54,6 +54,6 @@ .mainButtonsContainer { display: flex; gap: 0 16px; - margin-bottom: 10px + margin: 40px auto } \ No newline at end of file diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index f87bd3ee8..7376f5c8e 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -164,6 +164,23 @@ export const SplashScreen: React.FC = ({ explorer }) => { const container = explorer; const subscriptions: Array<{ dispose: () => void }> = []; + let title: string; + let subtitle: string; + + switch (userContext.apiType) { + case "Postgres": + title = "Welcome to Azure Cosmos DB for PostgreSQL"; + subtitle = "Get started with our sample datasets, documentation, and additional tools."; + break; + case "VCoreMongo": + title = "Welcome to Azure DocumentDB (with MongoDB compatibility)"; + subtitle = "Get started with our sample datasets, documentation, and additional tools."; + break; + default: + title = "Welcome to Azure Cosmos DB"; + subtitle = "Globally distributed, multi-model database service for any scale"; + } + React.useEffect(() => { subscriptions.push( { @@ -902,10 +919,11 @@ export const SplashScreen: React.FC = ({ explorer }) => { return (
-

- Welcome to Azure Cosmos DB +

+ {title} +

-
Globally distributed, multi-model database service for any scale
+
{subtitle}
{getSplashScreenButtons()} {useCarousel.getState().showCoachMark && ( => { }); }; +export const openRestoreContainerDialog = (): void => { + if (isFabricNative()) { + sendCachedDataMessage(FabricMessageTypes.RestoreContainer, []); + } +}; + /** * Check token validity and schedule a refresh if necessary * @param tokenTimestamp diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index b5e324116..2eecd5033 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -40,6 +40,7 @@ export type Features = { readonly disableConnectionStringLogin: boolean; readonly enableContainerCopy: boolean; readonly enableCloudShell: boolean; + readonly enableRestoreContainer: boolean; // only for Fabric // can be set via both flight and feature flag autoscaleDefault: boolean; @@ -111,6 +112,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"), disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"), enableContainerCopy: "true" === get("enablecontainercopy"), + enableRestoreContainer: "true" === get("enablerestorecontainer"), enableCloudShell: true, }; } diff --git a/src/Utils/AuthorizationUtils.test.ts b/src/Utils/AuthorizationUtils.test.ts index 650f2ed17..781edec60 100644 --- a/src/Utils/AuthorizationUtils.test.ts +++ b/src/Utils/AuthorizationUtils.test.ts @@ -43,6 +43,7 @@ describe("AuthorizationUtils", () => { partitionKeyDefault: false, partitionKeyDefault2: false, notebooksDownBanner: false, + enableRestoreContainer: false, }, }); }; diff --git a/test/sql/indexAdvisor.spec.ts b/test/sql/indexAdvisor.spec.ts index 4d9ac6aa2..dc6ee978c 100644 --- a/test/sql/indexAdvisor.spec.ts +++ b/test/sql/indexAdvisor.spec.ts @@ -10,7 +10,7 @@ let CONTAINER_ID: string; // Set up test database and container with data before all tests test.beforeAll(async () => { - testContainer = await createTestSQLContainer(true); + testContainer = await createTestSQLContainer({ includeTestData: true }); DATABASE_ID = testContainer.database.id; CONTAINER_ID = testContainer.container.id; });