Compare commits

...

7 Commits

Author SHA1 Message Date
Sakshi Gupta
7fd6de9b49 updated the feature value to hardcode true 2026-01-22 13:44:47 +05:30
sakshigupta12feb
31385950dd removed NotebookViewer file (#2281)
Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
2026-01-22 00:07:06 +05:30
Laurent Nguyen
6dce2632c8 fix: prevent race condition by initializing scenario tracking after config is set (#2339)
Co-authored-by: Laurent Nguyen <languye@microsoft.com>
2026-01-21 17:28:08 +01:00
BChoudhury-ms
80ad5f10d4 update args type for FT data creation (#2330) 2026-01-21 15:59:26 +05:30
BChoudhury-ms
f02611c90e Fix: Disable Complete action when any job update is in progress (#2335)
* Fix: Disable Complete action when any job update is in progress

- Updated CopyJobActionMenu to disable Complete button when any action is being performed
- Added comprehensive tests to verify Complete button is disabled during updates
- Improved user experience by preventing concurrent actions on copy jobs

This PR was generated by GitHub Copilot CLI

* Fix lint error: Remove unused variable in CopyJobActionMenu (#2336)

* Initial plan

* Fix lint error: Remove unused variable 'updatingAction' in CopyJobActionMenu.tsx

Co-authored-by: BChoudhury-ms <201893606+BChoudhury-ms@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: BChoudhury-ms <201893606+BChoudhury-ms@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: BChoudhury-ms <201893606+BChoudhury-ms@users.noreply.github.com>
2026-01-21 11:55:15 +05:30
Dmitry Shilov
9646dfcf04 feat: Enhance restore container functionality and UI updates (#2217) 2026-01-20 14:15:53 +01:00
sakshigupta12feb
90f3c3a79e Reverted the header sub header logic (#2337)
* updated the text

* updated margin

---------

Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
2026-01-19 21:16:28 +05:30
12 changed files with 86 additions and 103 deletions

View File

@@ -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";

View File

@@ -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(<TestComponentWrapper job={job} />);
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");
});
});

View File

@@ -61,7 +61,6 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ 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<CopyJobActionMenuProps> = ({ job, handleClick
text: ContainerCopyMessages.MonitorJobs.Actions.complete,
iconProps: { iconName: "CheckMark" },
onClick: () => showActionConfirmationDialog(job, CopyJobActions.complete),
disabled: isThisJobUpdating && updatingAction === CopyJobActions.complete,
disabled: isThisJobUpdating,
});
}
return filteredItems;

View File

@@ -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,

View File

@@ -54,6 +54,6 @@
.mainButtonsContainer {
display: flex;
gap: 0 16px;
margin-bottom: 10px
margin: 40px auto
}

View File

@@ -164,6 +164,23 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({ 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<SplashScreenProps> = ({ explorer }) => {
return (
<div className={styles.splashScreenContainer}>
<div className={styles.splashScreen}>
<h2 className={styles.title} role="heading" aria-label="Welcome to Azure Cosmos DB">
Welcome to Azure Cosmos DB<span className="activePatch"></span>
<h2 className={styles.title} role="heading" aria-label={title}>
{title}
<span className="activePatch"></span>
</h2>
<div className={styles.subtitle}>Globally distributed, multi-model database service for any scale</div>
<div className={styles.subtitle}>{subtitle}</div>
{getSplashScreenButtons()}
{useCarousel.getState().showCoachMark && (
<Coachmark

View File

@@ -105,9 +105,12 @@ const App = (): JSX.Element => {
// Scenario-based health tracking: start ApplicationLoad and complete phases.
const { startScenario, completePhase } = useMetricScenario();
React.useEffect(() => {
startScenario(MetricScenario.ApplicationLoad);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Only start scenario after config is initialized to avoid race conditions
// with message handlers that depend on configContext.platform
if (config) {
startScenario(MetricScenario.ApplicationLoad);
}
}, [config, startScenario]);
React.useEffect(() => {
if (explorer) {

View File

@@ -1,88 +0,0 @@
import { initializeIcons } from "@fluentui/react";
import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import * as ReactDOM from "react-dom";
import { configContext, initializeConfiguration } from "../ConfigContext";
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
import { GalleryTab } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import {
NotebookViewerComponent,
NotebookViewerComponentProps,
} from "../Explorer/Controls/NotebookViewer/NotebookViewerComponent";
import * as FileSystemUtil from "../Explorer/Notebook/FileSystemUtil";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "../Utils/GalleryUtils";
const onInit = async () => {
initializeIcons();
await initializeConfiguration();
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search);
let backNavigationText: string;
let onBackClick: () => void;
if (galleryViewerProps.selectedTab !== undefined) {
backNavigationText = GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
onBackClick = () =>
(window.location.href = `${configContext.hostedExplorerURL}gallery.html?tab=${
GalleryTab[galleryViewerProps.selectedTab]
}`);
}
const hideInputs = notebookViewerProps.hideInputs;
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
const galleryItemId = notebookViewerProps.galleryItemId;
let galleryItem: IGalleryItem;
if (galleryItemId) {
const junoClient = new JunoClient();
const galleryItemJunoResponse = await junoClient.getNotebookInfo(galleryItemId);
galleryItem = galleryItemJunoResponse.data;
}
// The main purpose of hiding the prompt is to hide everything when hiding inputs.
// It is generally not very useful to just hide the prompt.
const hidePrompts = hideInputs;
render(notebookUrl, backNavigationText, hideInputs, hidePrompts, galleryItem, onBackClick);
};
const render = (
notebookUrl: string,
backNavigationText: string,
hideInputs?: boolean,
hidePrompts?: boolean,
galleryItem?: IGalleryItem,
onBackClick?: () => void,
) => {
const props: NotebookViewerComponentProps = {
junoClient: galleryItem ? new JunoClient() : undefined,
notebookUrl,
galleryItem,
backNavigationText,
hideInputs,
hidePrompts,
onBackClick: onBackClick,
onTagClick: undefined,
};
if (galleryItem) {
document.title = FileSystemUtil.stripExtension(galleryItem.name, "ipynb");
}
const element = (
<>
<header>
<GalleryHeaderComponent />
</header>
<div style={{ marginLeft: 120, marginRight: 120 }}>
<NotebookViewerComponent {...props} />
</div>
</>
);
ReactDOM.render(element, document.getElementById("notebookContent"));
};
// Entry point
window.addEventListener("load", onInit);

View File

@@ -105,6 +105,12 @@ const requestAndStoreAccessToken = async (): Promise<void> => {
});
};
export const openRestoreContainerDialog = (): void => {
if (isFabricNative()) {
sendCachedDataMessage(FabricMessageTypes.RestoreContainer, []);
}
};
/**
* Check token validity and schedule a refresh if necessary
* @param tokenTimestamp

View File

@@ -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;
@@ -93,7 +94,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
notebookBasePath: get("notebookbasepath"),
notebookServerToken: get("notebookservertoken"),
notebookServerUrl: get("notebookserverurl"),
sandboxNotebookOutputs: "true" === get("sandboxnotebookoutputs", "true"),
sandboxNotebookOutputs: true,
selfServeType: get("selfservetype"),
showMinRUSurvey: "true" === get("showminrusurvey"),
ttl90Days: "true" === get("ttl90days"),
@@ -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,
};
}

View File

@@ -27,7 +27,7 @@ describe("AuthorizationUtils", () => {
enableKoResourceTree: false,
enableThroughputBuckets: false,
hostedDataExplorer: false,
sandboxNotebookOutputs: false,
sandboxNotebookOutputs: true,
showMinRUSurvey: false,
ttl90Days: false,
enableThroughputCap: false,
@@ -43,6 +43,7 @@ describe("AuthorizationUtils", () => {
partitionKeyDefault: false,
partitionKeyDefault2: false,
notebooksDownBanner: false,
enableRestoreContainer: false,
},
});
};

View File

@@ -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;
});