diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index c627c65fd..b3671a8b7 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -96,7 +96,6 @@ export class Flights { public static readonly AutoscaleTest = "autoscaletest"; public static readonly PartitionKeyTest = "partitionkeytest"; public static readonly PKPartitionKeyTest = "pkpartitionkeytest"; - public static readonly Phoenix = "phoenix"; public static readonly NotebooksDownBanner = "notebooksdownbanner"; } diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index 5f5d9cc01..2a0d8c6a5 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -438,14 +438,16 @@ export interface ContainerInfo { } export interface IProvisionData { - subscriptionId: string; - resourceGroup: string; - dbAccountName: string; cosmosEndpoint: string; } -export interface IContainerData { +export interface IAccountData { + subscriptionId: string; + resourceGroup: string; dbAccountName: string; +} + +export interface IContainerData extends IAccountData { forwardingId: string; } diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index a52254cc3..76e291ab6 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -83,7 +83,7 @@ export const createCollectionContextMenuButton = ( items.push({ iconSrc: HostedTerminalIcon, - isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown, + isDisabled: useNotebook.getState().isShellEnabled && useNotebook.getState().isPhoenix === false, onClick: () => { const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); if (useNotebook.getState().isShellEnabled) { diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index f100d7b7a..a87764d4b 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -14,7 +14,14 @@ import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHand import * as Logger from "../Common/Logger"; import { QueriesClient } from "../Common/QueriesClient"; import * as DataModels from "../Contracts/DataModels"; -import { ContainerConnectionInfo, IPhoenixConnectionInfoResult, IResponse } from "../Contracts/DataModels"; +import { + ContainerConnectionInfo, + IAccountData, + IContainerData, + IPhoenixConnectionInfoResult, + IProvisionData, + IResponse, +} from "../Contracts/DataModels"; import * as ViewModels from "../Contracts/ViewModels"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { useSidePanel } from "../hooks/useSidePanel"; @@ -30,7 +37,6 @@ import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import { get as getWorkspace, listByDatabaseAccount, - listConnectionInfo, start, } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; import { stringToBlob } from "../Utils/BlobUtils"; @@ -347,29 +353,11 @@ export default class Explorer { if (!databaseAccount) { throw new Error("No database account specified"); } - if (this._isInitializingNotebooks) { return; } this._isInitializingNotebooks = true; - if (userContext.features.phoenix === false) { - await this.ensureNotebookWorkspaceRunning(); - const connectionInfo = await listConnectionInfo( - userContext.subscriptionId, - userContext.resourceGroup, - databaseAccount.name, - "default" - ); - - useNotebook.getState().setNotebookServerInfo({ - notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint, - authToken: userContext.features.notebookServerToken || connectionInfo.authToken, - forwardingId: undefined, - }); - } - this.refreshNotebookList(); - this._isInitializingNotebooks = false; } @@ -381,11 +369,13 @@ export default class Explorer { (notebookServerInfo === undefined || (notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined)) ) { - const provisionData = { + const provisionData: IProvisionData = { + cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, + }; + const accountData: IAccountData = { subscriptionId: userContext.subscriptionId, resourceGroup: userContext.resourceGroup, dbAccountName: userContext.databaseAccount.name, - cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, }; const connectionStatus: ContainerConnectionInfo = { status: ConnectionStatusType.Connecting, @@ -393,7 +383,7 @@ export default class Explorer { useNotebook.getState().setConnectionInfo(connectionStatus); try { useNotebook.getState().setIsAllocating(true); - const connectionInfo = await this.phoenixClient.allocateContainer(provisionData); + const connectionInfo = await this.phoenixClient.allocateContainer(provisionData, accountData); await this.setNotebookInfo(connectionInfo, connectionStatus); } catch (error) { connectionStatus.status = ConnectionStatusType.Failed; @@ -412,9 +402,11 @@ export default class Explorer { connectionStatus: DataModels.ContainerConnectionInfo ) { if (connectionInfo.status === HttpStatusCodes.OK && connectionInfo.data && connectionInfo.data.notebookServerUrl) { - const containerData = { - forwardingId: connectionInfo.data.forwardingId, + const containerData: IContainerData = { + subscriptionId: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, dbAccountName: userContext.databaseAccount.name, + forwardingId: connectionInfo.data.forwardingId, }; await this.phoenixClient.initiateContainerHeartBeat(containerData); @@ -443,14 +435,10 @@ export default class Explorer { ); return; } - const dialogContent = NotebookUtil.isPhoenixEnabled() - ? "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?" - : "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?"; - const resetConfirmationDialogProps: DialogProps = { isModal: true, title: "Reset Workspace", - subText: dialogContent, + subText: "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?", primaryButtonText: "OK", secondaryButtonText: "Cancel", onPrimaryButtonClick: this._resetNotebookWorkspace, @@ -517,17 +505,14 @@ export default class Explorer { logConsoleError(error); return; } - - if (NotebookUtil.isPhoenixEnabled()) { - useTabs.getState().closeAllNotebookTabs(true); - connectionStatus = { - status: ConnectionStatusType.Connecting, - }; - useNotebook.getState().setConnectionInfo(connectionStatus); - } + useTabs.getState().closeAllNotebookTabs(true); + connectionStatus = { + status: ConnectionStatusType.Connecting, + }; + useNotebook.getState().setConnectionInfo(connectionStatus); const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace(); if (connectionInfo && connectionInfo.status && connectionInfo.status === HttpStatusCodes.OK) { - if (NotebookUtil.isPhoenixEnabled() && connectionInfo.data && connectionInfo.data.notebookServerUrl) { + if (connectionInfo.data && connectionInfo.data.notebookServerUrl) { await this.setNotebookInfo(connectionInfo, connectionStatus); useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); } @@ -536,13 +521,11 @@ export default class Explorer { } else { logConsoleError(`Failed to reset notebook workspace`); TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace); - if (NotebookUtil.isPhoenixEnabled()) { - connectionStatus = { - status: ConnectionStatusType.Reconnect, - }; - useNotebook.getState().resetContainerConnection(connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } + connectionStatus = { + status: ConnectionStatusType.Reconnect, + }; + useNotebook.getState().resetContainerConnection(connectionStatus); + useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); } } catch (error) { logConsoleError(`Failed to reset notebook workspace: ${error}`); @@ -550,13 +533,11 @@ export default class Explorer { error: getErrorMessage(error), errorStack: getErrorStack(error), }); - if (NotebookUtil.isPhoenixEnabled()) { - connectionStatus = { - status: ConnectionStatusType.Failed, - }; - useNotebook.getState().setConnectionInfo(connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } + connectionStatus = { + status: ConnectionStatusType.Failed, + }; + useNotebook.getState().setConnectionInfo(connectionStatus); + useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); throw error; } finally { clearInProgressMessage(); @@ -748,7 +729,7 @@ export default class Explorer { if (!notebookContentItem || !notebookContentItem.path) { throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); } - if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) { + if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenix) { await this.allocateContainer(); } @@ -972,7 +953,7 @@ export default class Explorer { handleError(error, "Explorer/onNewNotebookClicked"); throw new Error(error); } - const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled(); + const isPhoenixEnabled = useNotebook.getState().isPhoenix; if (isPhoenixEnabled) { if (isGithubTree) { async () => { @@ -1064,7 +1045,7 @@ export default class Explorer { } public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { await this.allocateContainer(); const notebookServerInfo = useNotebook.getState().notebookServerInfo; if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) { @@ -1204,7 +1185,7 @@ export default class Explorer { } public async handleOpenFileAction(path: string): Promise { - if (userContext.features.phoenix) { + if (useNotebook.getState().isPhoenix) { await this.allocateContainer(); } else if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) { this._openSetupNotebooksPaneForQuickstart(); @@ -1238,7 +1219,7 @@ export default class Explorer { } public openUploadFilePanel(parent?: NotebookContentItem): void { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { useDialog.getState().showOkCancelModalDialog( Notebook.newNotebookUploadModalTitle, undefined, @@ -1252,9 +1233,6 @@ export default class Explorer { undefined, this.getNewNoteWarningText() ); - } else { - parent = parent || this.resourceTree.myNotebooksContentRoot; - this.uploadFilePanel(parent); } } @@ -1268,7 +1246,7 @@ export default class Explorer { } public getDownloadModalConent(fileName: string): JSX.Element { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { return ( <>

{Notebook.galleryNotebookDownloadContent1}

@@ -1292,16 +1270,15 @@ export default class Explorer { await useNotebook.getState().refreshNotebooksEnabledStateForAccount(); // TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount - const isNotebookEnabled = userContext.features.notebooksDownBanner || userContext.features.phoenix; + const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenix; useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled); - useNotebook.getState().setIsShellEnabled(userContext.features.phoenix && isPublicInternetAccessAllowed()); + useNotebook.getState().setIsShellEnabled(useNotebook.getState().isPhoenix && isPublicInternetAccessAllowed()); TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { isNotebookEnabled, dataExplorerArea: Constants.Areas.Notebook, }); - - if (!userContext.features.notebooksTemporarilyDown) { + if (useNotebook.getState().isPhoenix) { if (isNotebookEnabled) { await this.initNotebooks(userContext.databaseAccount); } else if (this.notebookToImport) { diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index 222fe2fa4..50454be7d 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -4,15 +4,12 @@ * and update any knockout observables passed from the parent. */ import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; +import { useNotebook } from "Explorer/Notebook/useNotebook"; import * as React from "react"; import create, { UseStore } from "zustand"; import { StyleConstants } from "../../../Common/Constants"; -import * as ViewModels from "../../../Contracts/ViewModels"; -import { useTabs } from "../../../hooks/useTabs"; -import { userContext } from "../../../UserContext"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import Explorer from "../../Explorer"; -import { NotebookUtil } from "../../Notebook/NotebookUtil"; import { useSelectedNode } from "../../useSelectedNode"; import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import * as CommandBarUtil from "./CommandBarUtil"; @@ -56,18 +53,10 @@ export const CommandBar: React.FC = ({ container }: Props) => { const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus")); } - if ( - userContext.features.phoenix === false && - userContext.features.notebooksTemporarilyDown === false && - useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2 - ) { - uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker")); - } - return (
{ it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { useNotebook.getState().setIsNotebookEnabled(true); + useNotebook.getState().setIsPhoenix(true); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); expect(openMongoShellBtn).toBeDefined(); - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); + expect(openMongoShellBtn.disabled).toBe(false); + expect(openMongoShellBtn.tooltipText).toBe(""); }); it("Notebooks is enabled and is available - button should be shown and enabled", () => { useNotebook.getState().setIsNotebookEnabled(true); + useNotebook.getState().setIsPhoenix(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); + expect(openMongoShellBtn.disabled).toBe(false); + expect(openMongoShellBtn.tooltipText).toBe(""); }); it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { @@ -299,30 +296,27 @@ describe("CommandBarComponentButtonFactory tests", () => { it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { useNotebook.getState().setIsNotebookEnabled(true); + useNotebook.getState().setIsPhoenix(true); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeDefined(); - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); + expect(openCassandraShellBtn.disabled).toBe(false); + expect(openCassandraShellBtn.tooltipText).toBe(""); }); it("Notebooks is enabled and is available - button should be shown and enabled", () => { useNotebook.getState().setIsNotebookEnabled(true); + useNotebook.getState().setIsPhoenix(true); useNotebook.getState().setIsNotebooksEnabledForAccount(true); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); + expect(openCassandraShellBtn.disabled).toBe(false); + expect(openCassandraShellBtn.tooltipText).toBe(""); }); }); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 3a2495362..04739765f 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -10,7 +10,6 @@ import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg"; import GitHubIcon from "../../../../images/github.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; -import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg"; import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg"; @@ -98,7 +97,7 @@ export function createStaticCommandBarButtons( } notebookButtons.forEach((btn) => { - if (userContext.features.notebooksTemporarilyDown) { + if (useNotebook.getState().isPhoenix === false && userContext.features.notebooksDownBanner) { if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) { applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg); } else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) { @@ -109,11 +108,6 @@ export function createStaticCommandBarButtons( } buttons.push(btn); }); - } else { - if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) { - buttons.push(createDivider()); - buttons.push(createEnableNotebooksButton(container)); - } } if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) { @@ -168,7 +162,7 @@ export function createContextCommandBarButtons( onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); if (useNotebook.getState().isShellEnabled) { - if (!userContext.features.notebooksTemporarilyDown) { + if (useNotebook.getState().isPhoenix) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); } } else { @@ -179,12 +173,12 @@ export function createContextCommandBarButtons( ariaLabel: label, hasPopup: true, tooltipText: - useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown + useNotebook.getState().isShellEnabled && useNotebook.getState().isPhoenix === false ? Constants.Notebook.mongoShellTemporarilyDownMsg : undefined, disabled: (selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") || - (useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown), + (useNotebook.getState().isShellEnabled && useNotebook.getState().isPhoenix === false), }; buttons.push(newMongoShellBtn); } @@ -476,33 +470,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { }; } -function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps { - if (configContext.platform === Platform.Emulator) { - return undefined; - } - const label = "Enable Notebooks (Preview)"; - const tooltip = - "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const description = - "Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account."; - return { - iconSrc: EnableNotebooksIcon, - iconAlt: label, - onCommandClick: () => - useSidePanel - .getState() - .openSidePanel( - label, - - ), - commandButtonLabel: label, - hasPopup: false, - disabled: !useNotebook.getState().isNotebooksEnabledForAccount, - ariaLabel: label, - tooltipText: useNotebook.getState().isNotebooksEnabledForAccount ? "" : tooltip, - }; -} - function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { const label = "Open Terminal"; return { diff --git a/src/Explorer/Notebook/NotebookContainerClient.ts b/src/Explorer/Notebook/NotebookContainerClient.ts index 43a85df51..25f89190e 100644 --- a/src/Explorer/Notebook/NotebookContainerClient.ts +++ b/src/Explorer/Notebook/NotebookContainerClient.ts @@ -10,6 +10,7 @@ import * as Logger from "../../Common/Logger"; import * as DataModels from "../../Contracts/DataModels"; import { ContainerConnectionInfo, + IAccountData, IPhoenixConnectionInfoResult, IProvisionData, IResponse, @@ -18,7 +19,6 @@ import { userContext } from "../../UserContext"; import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; -import { NotebookUtil } from "./NotebookUtil"; import { useNotebook } from "./useNotebook"; export class NotebookContainerClient { @@ -63,7 +63,7 @@ export class NotebookContainerClient { this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs); } } catch (exception) { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { const connectionStatus: ContainerConnectionInfo = { status: ConnectionStatusType.Failed, }; @@ -131,14 +131,14 @@ export class NotebookContainerClient { } else if (response.status === HttpStatusCodes.NotFound) { throw new AbortError(response.statusText); } - throw new Error(); + throw new Error(response.statusText); } else { return undefined; } } private checkStatus(): boolean { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { if (useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Disconnected) { const connectionStatus: ContainerConnectionInfo = { status: ConnectionStatusType.Reconnect, @@ -173,21 +173,20 @@ export class NotebookContainerClient { } try { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { const provisionData: IProvisionData = { + cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, + }; + const accountData: IAccountData = { subscriptionId: userContext.subscriptionId, resourceGroup: userContext.resourceGroup, dbAccountName: userContext.databaseAccount.name, - cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, }; - return await this.phoenixClient.resetContainer(provisionData); + return await this.phoenixClient.resetContainer(provisionData, accountData); } return null; } catch (error) { Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace"); - if (!NotebookUtil.isPhoenixEnabled()) { - await this.recreateNotebookWorkspaceAsync(); - } throw error; } } diff --git a/src/Explorer/Notebook/NotebookUtil.ts b/src/Explorer/Notebook/NotebookUtil.ts index 68d562f38..ca920cacf 100644 --- a/src/Explorer/Notebook/NotebookUtil.ts +++ b/src/Explorer/Notebook/NotebookUtil.ts @@ -1,9 +1,9 @@ import { ImmutableCodeCell, ImmutableNotebook } from "@nteract/commutable"; import { AppState, selectors } from "@nteract/core"; import domtoimage from "dom-to-image"; +import { useNotebook } from "Explorer/Notebook/useNotebook"; import Html2Canvas from "html2canvas"; import path from "path"; -import { userContext } from "../../UserContext"; import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as StringUtils from "../../Utils/StringUtils"; import { SnapshotFragment } from "./NotebookComponent/types"; @@ -331,14 +331,10 @@ export class NotebookUtil { } public static getNotebookBtnTitle(fileName: string): string { - if (this.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { return `Download to ${fileName}`; } else { return `Download to my notebooks`; } } - - public static isPhoenixEnabled(): boolean { - return userContext.features.notebooksTemporarilyDown === false && userContext.features.phoenix === true; - } } diff --git a/src/Explorer/Notebook/useNotebook.ts b/src/Explorer/Notebook/useNotebook.ts index b4f90bfb0..a7f4e2103 100644 --- a/src/Explorer/Notebook/useNotebook.ts +++ b/src/Explorer/Notebook/useNotebook.ts @@ -1,4 +1,5 @@ import { cloneDeep } from "lodash"; +import { PhoenixClient } from "Phoenix/PhoenixClient"; import create, { UseStore } from "zustand"; import { AuthType } from "../../AuthType"; import * as Constants from "../../Common/Constants"; @@ -7,7 +8,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils"; import * as Logger from "../../Common/Logger"; import { configContext } from "../../ConfigContext"; import * as DataModels from "../../Contracts/DataModels"; -import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels"; +import { ContainerConnectionInfo, ContainerInfo, IAccountData } from "../../Contracts/DataModels"; import { useTabs } from "../../hooks/useTabs"; import { IPinnedRepo } from "../../Juno/JunoClient"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; @@ -17,7 +18,6 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils"; import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem"; import NotebookManager from "./NotebookManager"; -import { NotebookUtil } from "./NotebookUtil"; interface NotebookState { isNotebookEnabled: boolean; @@ -37,6 +37,7 @@ interface NotebookState { isAllocating: boolean; isRefreshed: boolean; containerStatus: ContainerInfo; + isPhoenix: boolean; setIsNotebookEnabled: (isNotebookEnabled: boolean) => void; setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void; setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void; @@ -58,6 +59,8 @@ interface NotebookState { resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void; setIsRefreshed: (isAllocating: boolean) => void; setContainerStatus: (containerStatus: ContainerInfo) => void; + getPhoenixStatus: () => Promise; + setIsPhoenix: (isPhoenix: boolean) => void; } export const useNotebook: UseStore = create((set, get) => ({ @@ -92,6 +95,7 @@ export const useNotebook: UseStore = create((set, get) => ({ durationLeftInMinutes: undefined, notebookServerInfo: undefined, }, + isPhoenix: undefined, setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }), setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }), setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => @@ -104,6 +108,7 @@ export const useNotebook: UseStore = create((set, get) => ({ setNotebookBasePath: (notebookBasePath: string) => set({ notebookBasePath }), setNotebookFolderName: (notebookFolderName: string) => set({ notebookFolderName }), refreshNotebooksEnabledStateForAccount: async (): Promise => { + await get().getPhoenixStatus(); const { databaseAccount, authType } = userContext; if ( authType === AuthType.EncryptedToken || @@ -196,7 +201,7 @@ export const useNotebook: UseStore = create((set, get) => ({ isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root }); }, initializeNotebooksTree: async (notebookManager: NotebookManager): Promise => { - const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks"; + const notebookFolderName = "Temporary Notebooks"; set({ notebookFolderName }); const myNotebooksContentRoot = { name: get().notebookFolderName, @@ -210,10 +215,10 @@ export const useNotebook: UseStore = create((set, get) => ({ }; const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn() ? { - name: "GitHub repos", - path: "PsuedoDir", - type: NotebookContentItemType.Directory, - } + name: "GitHub repos", + path: "PsuedoDir", + type: NotebookContentItemType.Directory, + } : undefined; set({ @@ -292,4 +297,17 @@ export const useNotebook: UseStore = create((set, get) => ({ }, setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }), setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }), + getPhoenixStatus: async () => { + if (get().isPhoenix === undefined) { + const phoenixClient = new PhoenixClient(); + const accountData: IAccountData = { + subscriptionId: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + dbAccountName: userContext.databaseAccount.name, + }; + const isPhoenix = await phoenixClient.IsDbAcountWhitelisted(accountData); + set({ isPhoenix }); + } + }, + setIsPhoenix: (isPhoenix: boolean) => set({ isPhoenix }), })); diff --git a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx index 960f25845..7e7ff6415 100644 --- a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx +++ b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx @@ -5,7 +5,6 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { useSidePanel } from "../../../hooks/useSidePanel"; import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient"; -import { userContext } from "../../../UserContext"; import * as GitHubUtils from "../../../Utils/GitHubUtils"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import Explorer from "../../Explorer"; @@ -76,7 +75,7 @@ export const CopyNotebookPane: FunctionComponent = ({ selectedLocation.owner, selectedLocation.repo )} - ${selectedLocation.branch}`; - } else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) { + } else if (selectedLocation.type === "MyNotebooks") { destination = useNotebook.getState().notebookFolderName; } diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index 10866cf6f..822e0c2e5 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -84,10 +84,7 @@ export class SplashScreen extends React.Component { const mainItems = this.createMainItems(); const commonTaskItems = this.createCommonTaskItems(); let recentItems = this.createRecentItems(); - if (userContext.features.notebooksTemporarilyDown) { - recentItems = recentItems.filter((item) => item.description !== "Notebook"); - } - + recentItems = recentItems.filter((item) => item.description !== "Notebook"); const tipsItems = this.createTipsItems(); const onClearRecent = this.clearMostRecent; @@ -223,7 +220,7 @@ export class SplashScreen extends React.Component { }); } - if (useNotebook.getState().isNotebookEnabled && !userContext.features.notebooksTemporarilyDown) { + if (useNotebook.getState().isPhoenix) { heroes.push({ iconSrc: NewNotebookIcon, title: "New Notebook", diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 5f836f7fb..264dbc2bf 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -1,5 +1,5 @@ import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos"; -import { NotebookUtil } from "Explorer/Notebook/NotebookUtil"; +import { useNotebook } from "Explorer/Notebook/useNotebook"; import * as ko from "knockout"; import * as _ from "underscore"; import * as Constants from "../../Common/Constants"; @@ -529,7 +529,7 @@ export default class Collection implements ViewModels.Collection { }; public onSchemaAnalyzerClick = async () => { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { await this.container.allocateContainer(); } useSelectedNode.getState().setSelectedNode(this); diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index f31a4a52b..3e01dce44 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -121,18 +121,17 @@ export const ResourceTree: React.FC = ({ container }: Resourc children: [], }; - if (userContext.features.notebooksTemporarilyDown) { + if (useNotebook.getState().isPhoenix === false && userContext.features.notebooksDownBanner) { notebooksTree.children.push(buildNotebooksTemporarilyDownTree()); - } else { + } else if (useNotebook.getState().isPhoenix) { if (galleryContentRoot) { notebooksTree.children.push(buildGalleryNotebooksTree()); } if ( myNotebooksContentRoot && - ((NotebookUtil.isPhoenixEnabled() && - useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected) || - userContext.features.phoenix === false) + useNotebook.getState().isPhoenix && + useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected ) { notebooksTree.children.push(buildMyNotebooksTree()); } @@ -166,15 +165,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc const myNotebooksTree: TreeNode = buildNotebookDirectoryNode( myNotebooksContentRoot, (item: NotebookContentItem) => { - container.openNotebook(item).then((hasOpened) => { - if ( - hasOpened && - userContext.features.notebooksTemporarilyDown === false && - userContext.features.phoenix === false - ) { - mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); - } - }); + container.openNotebook(item); } ); @@ -189,15 +180,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode( gitHubNotebooksContentRoot, (item: NotebookContentItem) => { - container.openNotebook(item).then((hasOpened) => { - if ( - hasOpened && - userContext.features.notebooksTemporarilyDown === false && - userContext.features.phoenix === false - ) { - mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); - } - }); + container.openNotebook(item); }, true ); @@ -528,7 +511,7 @@ export const ResourceTree: React.FC = ({ container }: Resourc isNotebookEnabled && userContext.apiType === "Mongo" && isPublicInternetAccessAllowed() && - !userContext.features.notebooksTemporarilyDown + useNotebook.getState().isPhoenix ) { children.push({ label: "Schema (Preview)", diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts index b7fd39955..7a2048131 100644 --- a/src/Phoenix/PhoenixClient.ts +++ b/src/Phoenix/PhoenixClient.ts @@ -5,6 +5,7 @@ import * as Logger from "../Common/Logger"; import { configContext } from "../ConfigContext"; import { ContainerInfo, + IAccountData, IContainerData, IPhoenixConnectionInfoResult, IProvisionData, @@ -22,24 +23,36 @@ export class PhoenixClient { minTimeout: Notebook.retryAttemptDelayMs, }; - public async allocateContainer(provisionData: IProvisionData): Promise> { - return this.executeContainerAssignmentOperation(provisionData, "allocate"); + public async allocateContainer( + provisionData: IProvisionData, + accountData: IAccountData + ): Promise> { + return this.executeContainerAssignmentOperation(provisionData, accountData, "allocate"); } - public async resetContainer(provisionData: IProvisionData): Promise> { - return this.executeContainerAssignmentOperation(provisionData, "reset"); + public async resetContainer( + provisionData: IProvisionData, + accountData: IAccountData + ): Promise> { + return this.executeContainerAssignmentOperation(provisionData, accountData, "reset"); } private async executeContainerAssignmentOperation( provisionData: IProvisionData, + accountData: IAccountData, operation: string ): Promise> { try { - const response = await fetch(`${this.getPhoenixContainerPoolingEndPoint()}/${operation}`, { - method: "POST", - headers: PhoenixClient.getHeaders(), - body: JSON.stringify(provisionData), - }); + const response = await fetch( + `${this.getPhoenixContainerPoolingEndPoint()}/subscriptions/${accountData.subscriptionId}/resourceGroups/${ + accountData.resourceGroup + }/providers/Microsoft.DocumentDB/databaseAccounts/${accountData.dbAccountName}/containerconnections`, + { + method: operation === "allocate" ? "POST" : "PATCH", + headers: PhoenixClient.getHeaders(), + body: JSON.stringify(provisionData), + } + ); let data: IPhoenixConnectionInfoResult; if (response.status === HttpStatusCodes.OK) { @@ -55,7 +68,7 @@ export class PhoenixClient { } } - public async initiateContainerHeartBeat(containerData: { forwardingId: string; dbAccountName: string }) { + public async initiateContainerHeartBeat(containerData: IContainerData) { if (this.containerHealthHandler) { clearTimeout(this.containerHealthHandler); } @@ -72,7 +85,11 @@ export class PhoenixClient { try { const runContainerStatusAsync = async () => { const response = await window.fetch( - `${this.getPhoenixContainerPoolingEndPoint()}/${containerData.dbAccountName}/${containerData.forwardingId}`, + `${this.getPhoenixContainerPoolingEndPoint()}/subscriptions/${containerData.subscriptionId}/resourceGroups/${ + containerData.resourceGroup + }/providers/Microsoft.DocumentDB/databaseAccounts/${containerData.dbAccountName}/${ + containerData.forwardingId + }`, { method: "GET", headers: PhoenixClient.getHeaders(), @@ -101,7 +118,7 @@ export class PhoenixClient { } } - private async getContainerHealth(delayMs: number, containerData: { forwardingId: string; dbAccountName: string }) { + private async getContainerHealth(delayMs: number, containerData: IContainerData) { try { const containerInfo = await this.getContainerStatusAsync(containerData); useNotebook.getState().setContainerStatus(containerInfo); @@ -117,6 +134,24 @@ export class PhoenixClient { } } + public async IsDbAcountWhitelisted(accountData: IAccountData) { + try { + const response = await window.fetch( + `${this.getPhoenixContainerPoolingEndPoint()}/subscriptions/${accountData.subscriptionId}/resourceGroups/${ + accountData.resourceGroup + }/providers/Microsoft.DocumentDB/databaseAccounts/${accountData.dbAccountName}`, + { + method: "GET", + headers: PhoenixClient.getHeaders(), + } + ); + return response.status === HttpStatusCodes.OK; + } catch (error) { + Logger.logError(getErrorMessage(error), "PhoenixClient/IsDbAcountWhitelisted"); + return false; + } + } + public static getPhoenixEndpoint(): string { const phoenixEndpoint = userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 655b9430a..899939b54 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -11,7 +11,6 @@ export type Features = { autoscaleDefault: boolean; partitionKeyDefault: boolean; partitionKeyDefault2: boolean; - phoenix: boolean; notebooksDownBanner: boolean; readonly enableSDKoperations: boolean; readonly enableSpark: boolean; @@ -33,7 +32,6 @@ export type Features = { readonly ttl90Days: boolean; readonly mongoProxyEndpoint?: string; readonly mongoProxyAPIs?: string; - readonly notebooksTemporarilyDown: boolean; readonly enableThroughputCap: boolean; }; @@ -84,8 +82,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear autoscaleDefault: "true" === get("autoscaledefault"), partitionKeyDefault: "true" === get("partitionkeytest"), partitionKeyDefault2: "true" === get("pkpartitionkeytest"), - notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"), - phoenix: "true" === get("phoenix"), notebooksDownBanner: "true" === get("notebooksDownBanner"), enableThroughputCap: "true" === get("enablethroughputcap"), }; diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts index 060b24f3f..11e0925e5 100644 --- a/src/Utils/GalleryUtils.ts +++ b/src/Utils/GalleryUtils.ts @@ -10,7 +10,6 @@ import { SortBy, } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; import Explorer from "../Explorer/Explorer"; -import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil"; import { useNotebook } from "../Explorer/Notebook/useNotebook"; import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; @@ -229,7 +228,7 @@ export function downloadItem( undefined, "Download", async () => { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { await container.allocateContainer(); } const notebookServerInfo = useNotebook.getState().notebookServerInfo; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 30445225c..276feb8c5 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -339,9 +339,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) { userContext.features.partitionKeyDefault2 = true; } - if (inputs.flights.indexOf(Flights.Phoenix) !== -1) { - userContext.features.phoenix = true; - } if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) { userContext.features.notebooksDownBanner = true; }