diff --git a/configs/mpac.json b/configs/mpac.json index 7c270c6d5..f0c3654f6 100644 --- a/configs/mpac.json +++ b/configs/mpac.json @@ -1,3 +1,4 @@ { - "JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com" + "JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", + "isTerminalEnabled" : true } \ No newline at end of file diff --git a/configs/prod.json b/configs/prod.json index e2614b018..b6716b0c3 100644 --- a/configs/prod.json +++ b/configs/prod.json @@ -1,3 +1,4 @@ { - "JUNO_ENDPOINT": "https://tools.cosmos.azure.com" + "JUNO_ENDPOINT": "https://tools.cosmos.azure.com", + "isTerminalEnabled" : false } diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index f89ed5d61..61780c128 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -27,6 +27,7 @@ export interface ConfigContext { GITHUB_CLIENT_ID: string; GITHUB_TEST_ENV_CLIENT_ID: string; GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. + isTerminalEnabled: boolean; hostedExplorerURL: string; armAPIVersion?: string; allowedJunoOrigins: string[]; @@ -59,6 +60,7 @@ let configContext: Readonly = { GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772 JUNO_ENDPOINT: "https://tools.cosmos.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", + isTerminalEnabled: false, allowedJunoOrigins: [ JunoEndpoints.Test, JunoEndpoints.Test2, diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index 5f5d9cc01..64a9575d3 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -438,14 +438,10 @@ export interface ContainerInfo { } export interface IProvisionData { - subscriptionId: string; - resourceGroup: string; - dbAccountName: string; cosmosEndpoint: string; } export interface IContainerData { - dbAccountName: string; forwardingId: string; } diff --git a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx index ccaf18b83..1a9348701 100644 --- a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx +++ b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx @@ -17,7 +17,6 @@ import Explorer from "../../Explorer"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; -import { NotebookUtil } from "../../Notebook/NotebookUtil"; import { useNotebook } from "../../Notebook/useNotebook"; import { Dialog, TextFieldProps, useDialog } from "../Dialog"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; @@ -148,9 +147,7 @@ export class NotebookViewerComponent { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { await this.allocateContainer(); const notebookServerInfo = useNotebook.getState().notebookServerInfo; if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) { @@ -1214,7 +1197,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(); @@ -1248,7 +1231,7 @@ export default class Explorer { } public openUploadFilePanel(parent?: NotebookContentItem): void { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { useDialog.getState().showOkCancelModalDialog( Notebook.newNotebookUploadModalTitle, undefined, @@ -1278,7 +1261,7 @@ export default class Explorer { } public getDownloadModalConent(fileName: string): JSX.Element { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { return ( <>

{Notebook.galleryNotebookDownloadContent1}

@@ -1302,22 +1285,17 @@ 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 (isNotebookEnabled) { - await this.initNotebooks(userContext.databaseAccount); - } else if (this.notebookToImport) { - // if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane - this._openSetupNotebooksPaneForQuickstart(); - } + if (useNotebook.getState().isPhoenix) { + await this.initNotebooks(userContext.databaseAccount); } } } 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 (
{ - try { - const memoryUsageInfo = await this.getMemoryUsage(); - useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo); - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (notebookServerInfo?.notebookServerEndpoint) { - this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs); - } - } catch (exception) { - if (NotebookUtil.isPhoenixEnabled()) { - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Failed, - }; - useNotebook.getState().resetContainerConnection(connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } + const memoryUsageInfo = await this.getMemoryUsage(); + useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo); + const notebookServerInfo = useNotebook.getState().notebookServerInfo; + if (notebookServerInfo?.notebookServerEndpoint) { + this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs); } }, delayMs); } @@ -108,7 +91,7 @@ export class NotebookContainerClient { notebookServerEndpoint: string, authToken: string ): Promise { - if (this.checkStatus()) { + if (this.shouldExecuteMemoryCall()) { const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, { method: "GET", headers: { @@ -131,24 +114,17 @@ 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().containerStatus?.status === Constants.ContainerStatusType.Disconnected) { - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Reconnect, - }; - useNotebook.getState().resetContainerConnection(connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - return false; - } - } - return true; + private shouldExecuteMemoryCall(): boolean { + return ( + useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Active && + useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected + ); } public async resetWorkspace(): Promise> { @@ -173,11 +149,8 @@ export class NotebookContainerClient { } try { - if (NotebookUtil.isPhoenixEnabled()) { + if (useNotebook.getState().isPhoenix) { const provisionData: IProvisionData = { - subscriptionId: userContext.subscriptionId, - resourceGroup: userContext.resourceGroup, - dbAccountName: userContext.databaseAccount.name, cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint, }; return await this.phoenixClient.resetContainer(provisionData); @@ -185,9 +158,6 @@ export class NotebookContainerClient { return null; } catch (error) { Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace"); - if (!NotebookUtil.isPhoenixEnabled()) { - await this.recreateNotebookWorkspaceAsync(); - } throw error; } } @@ -202,25 +172,6 @@ export class NotebookContainerClient { }; } - private async recreateNotebookWorkspaceAsync(): Promise { - const { databaseAccount } = userContext; - if (!databaseAccount?.id) { - throw new Error("DataExplorer not initialized"); - } - try { - await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default"); - await createOrUpdate( - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount.name, - "default" - ); - } catch (error) { - Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync"); - return Promise.reject(error); - } - } - private getHeaders(): HeadersInit { const authorizationHeader = getAuthorizationHeader(); return { diff --git a/src/Explorer/Notebook/NotebookUtil.ts b/src/Explorer/Notebook/NotebookUtil.ts index 68d562f38..b060533d7 100644 --- a/src/Explorer/Notebook/NotebookUtil.ts +++ b/src/Explorer/Notebook/NotebookUtil.ts @@ -3,7 +3,6 @@ import { AppState, selectors } from "@nteract/core"; import domtoimage from "dom-to-image"; 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"; @@ -329,16 +328,4 @@ export class NotebookUtil { link.click(); document.body.removeChild(link); } - - public static getNotebookBtnTitle(fileName: string): string { - if (this.isPhoenixEnabled()) { - 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..bc2a74372 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"; @@ -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 = get().isPhoenix ? "Temporary Notebooks" : "My Notebooks"; set({ notebookFolderName }); const myNotebooksContentRoot = { name: get().notebookFolderName, @@ -292,4 +297,15 @@ export const useNotebook: UseStore = create((set, get) => ({ }, setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }), setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }), + getPhoenixStatus: async () => { + if (get().isPhoenix === undefined) { + let isPhoenix = false; + if (userContext.features.phoenix) { + const phoenixClient = new PhoenixClient(); + isPhoenix = await phoenixClient.isDbAcountWhitelisted(); + } + 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..3971abe7e 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" && useNotebook.getState().isPhoenix) { destination = useNotebook.getState().notebookFolderName; } diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index 031214867..c414dc09b 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -84,9 +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; diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 58c06f521..8cfa4ef13 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 fcc82331e..1e64aa26d 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -130,9 +130,8 @@ export const ResourceTree: React.FC = ({ container }: Resourc 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 ); diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts index b7fd39955..55bd8a226 100644 --- a/src/Phoenix/PhoenixClient.ts +++ b/src/Phoenix/PhoenixClient.ts @@ -1,9 +1,18 @@ import promiseRetry, { AbortError } from "p-retry"; -import { ContainerStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../Common/Constants"; +import { Action } from "Shared/Telemetry/TelemetryConstants"; +import { + Areas, + ConnectionStatusType, + ContainerStatusType, + HttpHeaders, + HttpStatusCodes, + Notebook, +} from "../Common/Constants"; import { getErrorMessage } from "../Common/ErrorHandlingUtils"; import * as Logger from "../Common/Logger"; import { configContext } from "../ConfigContext"; import { + ContainerConnectionInfo, ContainerInfo, IContainerData, IPhoenixConnectionInfoResult, @@ -11,6 +20,7 @@ import { IResponse, } from "../Contracts/DataModels"; import { useNotebook } from "../Explorer/Notebook/useNotebook"; +import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../UserContext"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; @@ -35,8 +45,8 @@ export class PhoenixClient { operation: string ): Promise> { try { - const response = await fetch(`${this.getPhoenixContainerPoolingEndPoint()}/${operation}`, { - method: "POST", + const response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, { + method: operation === "allocate" ? "POST" : "PATCH", headers: PhoenixClient.getHeaders(), body: JSON.stringify(provisionData), }); @@ -55,7 +65,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 +82,7 @@ export class PhoenixClient { try { const runContainerStatusAsync = async () => { const response = await window.fetch( - `${this.getPhoenixContainerPoolingEndPoint()}/${containerData.dbAccountName}/${containerData.forwardingId}`, + `${this.getPhoenixControlPlanePathPrefix()}/${containerData.forwardingId}`, { method: "GET", headers: PhoenixClient.getHeaders(), @@ -86,13 +96,32 @@ export class PhoenixClient { status: ContainerStatusType.Active, }; } else if (response.status === HttpStatusCodes.NotFound) { + const error = "Disconnected from compute workspace"; + Logger.logError(error, ""); + const connectionStatus: ContainerConnectionInfo = { + status: ConnectionStatusType.Reconnect, + }; + TelemetryProcessor.traceMark(Action.PhoenixHeartBeat, { + dataExplorerArea: Areas.Notebook, + message: getErrorMessage(error), + }); + useNotebook.getState().resetContainerConnection(connectionStatus); + useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); throw new AbortError(response.statusText); } throw new Error(response.statusText); }; return await promiseRetry(runContainerStatusAsync, this.retryOptions); } catch (error) { - Logger.logError(getErrorMessage(error), "PhoenixClient/getContainerStatus"); + TelemetryProcessor.traceFailure(Action.PhoenixHeartBeat, { + dataExplorerArea: Areas.Notebook, + }); + Logger.logError(getErrorMessage(error), ""); + const connectionStatus: ContainerConnectionInfo = { + status: ConnectionStatusType.Failed, + }; + useNotebook.getState().resetContainerConnection(connectionStatus); + useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); return { durationLeftInMinutes: undefined, notebookServerInfo: undefined, @@ -101,19 +130,24 @@ export class PhoenixClient { } } - private async getContainerHealth(delayMs: number, containerData: { forwardingId: string; dbAccountName: string }) { + private async getContainerHealth(delayMs: number, containerData: IContainerData) { + const containerInfo = await this.getContainerStatusAsync(containerData); + useNotebook.getState().setContainerStatus(containerInfo); + if (useNotebook.getState().containerStatus?.status === ContainerStatusType.Active) { + this.scheduleContainerHeartbeat(delayMs, containerData); + } + } + + public async isDbAcountWhitelisted(): Promise { try { - const containerInfo = await this.getContainerStatusAsync(containerData); - useNotebook.getState().setContainerStatus(containerInfo); - if (useNotebook.getState().containerStatus?.status === ContainerStatusType.Active) { - this.scheduleContainerHeartbeat(delayMs, containerData); - } - } catch (exception) { - useNotebook.getState().setContainerStatus({ - durationLeftInMinutes: undefined, - notebookServerInfo: undefined, - status: ContainerStatusType.Disconnected, + const response = await window.fetch(`${this.getPhoenixControlPlanePathPrefix()}`, { + method: "GET", + headers: PhoenixClient.getHeaders(), }); + return response.status === HttpStatusCodes.OK; + } catch (error) { + Logger.logError(getErrorMessage(error), "PhoenixClient/IsDbAcountWhitelisted"); + return false; } } @@ -129,8 +163,10 @@ export class PhoenixClient { return phoenixEndpoint; } - public getPhoenixContainerPoolingEndPoint(): string { - return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer`; + public getPhoenixControlPlanePathPrefix(): string { + return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts${ + userContext.databaseAccount.id + }`; } private static getHeaders(): HeadersInit { diff --git a/src/Shared/Telemetry/TelemetryConstants.ts b/src/Shared/Telemetry/TelemetryConstants.ts index e9541b9f7..71bf6cb5a 100644 --- a/src/Shared/Telemetry/TelemetryConstants.ts +++ b/src/Shared/Telemetry/TelemetryConstants.ts @@ -82,6 +82,7 @@ export enum Action { NotebooksMoveCellUpFromMenu, NotebooksMoveCellDownFromMenu, PhoenixConnection, + PhoenixHeartBeat, PhoenixResetWorkspace, DeleteCellFromMenu, OpenTerminal, 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;