From 22da3b90ef845dbda23f117a21c13752514447c3 Mon Sep 17 00:00:00 2001
From: Karthik chakravarthy <88904658+kcheekuri@users.noreply.github.com>
Date: Fri, 22 Oct 2021 05:04:38 -0400
Subject: [PATCH] Phoenix Reconnect Integration (#1123)
* Reconnect integration
* git connection issue
* format issue
* Typo issue
* added constants
* Removed math.round for remainingTime
* code refctor for container status check
* disconnect text change
---
src/Common/Constants.ts | 14 +-
src/Contracts/DataModels.ts | 9 +-
src/Explorer/Controls/Dialog.tsx | 3 +-
.../NotebookTerminalComponent.test.tsx | 3 +
.../NotebookViewerComponent.tsx | 2 +-
src/Explorer/Explorer.tsx | 28 ++--
.../CommandBar/ConnectionStatusComponent.tsx | 126 ++++++++++++++----
.../Notebook/NotebookContainerClient.ts | 64 +++++----
.../Notebook/notebookClientV2.test.ts | 1 +
src/Explorer/Notebook/useNotebook.ts | 21 ++-
src/Explorer/Tabs/TerminalTab.tsx | 1 +
src/Phoenix/PhoenixClient.ts | 71 +++++++++-
src/Utils/GalleryUtils.ts | 2 +-
13 files changed, 268 insertions(+), 77 deletions(-)
diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts
index c9e94232b..545ae0965 100644
--- a/src/Common/Constants.ts
+++ b/src/Common/Constants.ts
@@ -347,6 +347,11 @@ export enum ConnectionStatusType {
ReConnect = "Reconnect",
}
+export enum ContainerStatusType {
+ Active = "Active",
+ InActive = "InActive",
+}
+
export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
@@ -357,20 +362,23 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
export class Notebook {
public static readonly defaultBasePath = "./notebooks";
public static readonly heartbeatDelayMs = 60000;
+ public static readonly containerStatusHeartbeatDelayMs = 30000;
public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 120000;
public static readonly memoryGuageToGB = 1048576;
+ public static readonly lowMemoryBar = 0.8;
+ public static readonly reminingTimeMin = 10;
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
public static readonly mongoShellTemporarilyDownMsg =
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
public static readonly cassandraShellTemporarilyDownMsg =
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
- public static saveNotebookModalTitle = "Save Notebook in temporary workspace";
+ public static saveNotebookModalTitle = "Save notebook in temporary workspace";
public static saveNotebookModalContent =
"This notebook will be saved in the temporary workspace and will be removed when the session expires. To save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your local machine before the session ends.";
- public static newNotebookModalTitle = "Create Notebook in temporary workspace";
- public static newNotebookUploadModalTitle = "Upload Notebook in temporary workspace";
+ public static newNotebookModalTitle = "Create notebook in temporary workspace";
+ public static newNotebookUploadModalTitle = "Upload notebook to temporary workspace";
public static newNotebookModalContent1 =
"A temporary workspace will be created to enable you to work with notebooks. When the session expires, any notebooks in the workspace will be removed.";
public static newNotebookModalContent2 =
diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts
index a8957c662..278a7ef5e 100644
--- a/src/Contracts/DataModels.ts
+++ b/src/Contracts/DataModels.ts
@@ -1,4 +1,4 @@
-import { ConnectionStatusType } from "../Common/Constants";
+import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
export interface DatabaseAccount {
id: string;
@@ -426,6 +426,13 @@ export interface OperationStatus {
export interface NotebookWorkspaceConnectionInfo {
authToken: string;
notebookServerEndpoint: string;
+ forwardingId: string;
+}
+
+export interface ContainerInfo {
+ durationLeftMin: number;
+ notebookServerInfo: NotebookWorkspaceConnectionInfo;
+ status: ContainerStatusType;
}
export interface NotebookWorkspaceFeedResponse {
diff --git a/src/Explorer/Controls/Dialog.tsx b/src/Explorer/Controls/Dialog.tsx
index 730a534f3..f8f716006 100644
--- a/src/Explorer/Controls/Dialog.tsx
+++ b/src/Explorer/Controls/Dialog.tsx
@@ -13,7 +13,6 @@ import {
Link,
PrimaryButton,
ProgressIndicator,
- Text,
TextField,
} from "@fluentui/react";
import React, { FC } from "react";
@@ -197,7 +196,7 @@ export const Dialog: FC = () => {
{linkProps.linkText}
)}
- {contentHtml && {contentHtml}}
+ {contentHtml}
{progressIndicatorProps && }
diff --git a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx b/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
index e991fe05c..d9747d6ee 100644
--- a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
+++ b/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
@@ -35,16 +35,19 @@ const testCassandraAccount: DataModels.DatabaseAccount = {
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: "authToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
+ forwardingId: "Id",
};
const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: "authToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
+ forwardingId: "Id",
};
const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: "authToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
+ forwardingId: "Id",
};
describe("NotebookTerminalComponent", () => {
diff --git a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
index e65c44445..ccaf18b83 100644
--- a/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
+++ b/src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
@@ -53,7 +53,7 @@ export class NotebookViewerComponent
super(props);
this.clientManager = new NotebookClientV2({
- connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined },
+ connectionInfo: { authToken: undefined, notebookServerEndpoint: undefined, forwardingId: undefined },
databaseAccountName: undefined,
defaultExperience: "NotebookViewer",
isReadOnly: true,
diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx
index 91f2d811e..57ee702ee 100644
--- a/src/Explorer/Explorer.tsx
+++ b/src/Explorer/Explorer.tsx
@@ -3,6 +3,7 @@ import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import * as ko from "knockout";
import React from "react";
import _ from "underscore";
+import shallow from "zustand/shallow";
import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants";
@@ -165,11 +166,9 @@ export default class Explorer {
);
useNotebook.subscribe(
- async () => {
- this.initiateAndRefreshNotebookList();
- useNotebook.getState().setIsRefreshed(false);
- },
- (state) => state.isNotebookEnabled || state.isRefreshed
+ async () => this.initiateAndRefreshNotebookList(),
+ (state) => [state.isNotebookEnabled, state.isRefreshed],
+ shallow
);
this.resourceTree = new ResourceTreeAdapter(this);
@@ -179,6 +178,7 @@ export default class Explorer {
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl,
authToken: userContext.features.notebookServerToken,
+ forwardingId: undefined,
});
}
@@ -364,6 +364,7 @@ export default class Explorer {
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
+ forwardingId: undefined,
});
}
@@ -395,11 +396,18 @@ export default class Explorer {
connectionInfo.data &&
connectionInfo.data.notebookServerUrl
) {
+ const containerData = {
+ forwardingId: connectionInfo.data.forwardingId,
+ dbAccountName: userContext.databaseAccount.name,
+ };
+ await this.phoenixClient.initiateContainerHeartBeat(containerData);
+
connectionStatus.status = ConnectionStatusType.Connected;
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
+ forwardingId: connectionInfo.data.forwardingId,
});
this.notebookManager?.notebookClient
.getMemoryUsage()
@@ -407,11 +415,11 @@ export default class Explorer {
useNotebook.getState().setIsAllocating(false);
} else {
connectionStatus.status = ConnectionStatusType.Failed;
- useNotebook.getState().resetConatinerConnection(connectionStatus);
+ useNotebook.getState().resetContainerConnection(connectionStatus);
}
} catch (error) {
connectionStatus.status = ConnectionStatusType.Failed;
- useNotebook.getState().resetConatinerConnection(connectionStatus);
+ useNotebook.getState().resetContainerConnection(connectionStatus);
throw error;
}
this.refreshNotebookList();
@@ -692,7 +700,7 @@ export default class Explorer {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
}
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
- this.allocateContainer();
+ await this.allocateContainer();
}
const notebookTabs = useTabs
@@ -1016,8 +1024,8 @@ export default class Explorer {
useDialog
.getState()
.showOkModalDialog(
- "Failed to Connect",
- "Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
+ "Failed to connect",
+ "Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again."
);
}
} else {
diff --git a/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx b/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
index 16b804a51..cb9d90b92 100644
--- a/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
+++ b/src/Explorer/Menus/CommandBar/ConnectionStatusComponent.tsx
@@ -1,8 +1,20 @@
-import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
-import { ActionButton } from "@fluentui/react/lib/Button";
+import {
+ FocusTrapCallout,
+ FocusZone,
+ FocusZoneTabbableElements,
+ FontWeights,
+ Icon,
+ mergeStyleSets,
+ ProgressIndicator,
+ Stack,
+ Text,
+ TooltipHost,
+} from "@fluentui/react";
+import { useId } from "@fluentui/react-hooks";
+import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button";
import * as React from "react";
import "../../../../less/hostedexplorer.less";
-import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
+import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook";
import "../CommandBar/ConnectionStatusComponent.less";
@@ -16,6 +28,26 @@ export const ConnectionStatus: React.FC = ({ container }: Props): JSX.Ele
const [counter, setCounter] = React.useState(0);
const [statusColor, setStatusColor] = React.useState("");
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
+ const [isBarDismissed, setIsBarDismissed] = React.useState(false);
+ const buttonId = useId("callout-button");
+ const containerInfo = useNotebook((state) => state.containerStatus);
+
+ const styles = mergeStyleSets({
+ callout: {
+ width: 320,
+ padding: "20px 24px",
+ },
+ title: {
+ marginBottom: 12,
+ fontWeight: FontWeights.semilight,
+ },
+ buttons: {
+ display: "flex",
+ justifyContent: "flex-end",
+ marginTop: 20,
+ },
+ });
+
React.useEffect(() => {
let intervalId: NodeJS.Timeout;
@@ -78,30 +110,70 @@ export const ConnectionStatus: React.FC = ({ container }: Props): JSX.Ele
setToolTipContent("Click here to Reconnect to temporary workspace.");
}
return (
- ) =>
- connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
- }
- >
-
-
-
-
- {connectionInfo.status}
-
- {connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
-
- )}
- {connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
- 0.8 ? "lowMemory" : ""}
- description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
- percentComplete={usedGB / totalGB}
- />
- )}
-
+ <>
+
+ ) =>
+ connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
+ }
+ >
+
+
+
+ {connectionInfo.status}
+
+ {connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
+
+ )}
+ {connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
+ Notebook.lowMemoryBar ? "lowMemory" : ""}
+ description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
+ percentComplete={usedGB / totalGB}
+ />
+ )}
+
+ {!isBarDismissed &&
+ containerInfo.status &&
+ containerInfo.status === ContainerStatusType.Active &&
+ containerInfo.durationLeftMin <= Notebook.reminingTimeMin ? (
+ setIsBarDismissed(true)}
+ setInitialFocus
+ >
+
+ Remaining time
+
+
+ This temporary workspace will get deleted in {Math.round(containerInfo.durationLeftMin)} minutes. To
+ save your work permanently, save your notebooks to a GitHub repository or download the notebooks to your
+ local machine before the session ends.
+
+
+
+ setIsBarDismissed(true)}>Dismiss
+
+
+
+ ) : undefined}
+
-
+ >
);
};
diff --git a/src/Explorer/Notebook/NotebookContainerClient.ts b/src/Explorer/Notebook/NotebookContainerClient.ts
index c0e6a04e8..79bf815ef 100644
--- a/src/Explorer/Notebook/NotebookContainerClient.ts
+++ b/src/Explorer/Notebook/NotebookContainerClient.ts
@@ -59,31 +59,27 @@ export class NotebookContainerClient {
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
try {
- const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
- method: "GET",
- headers: {
- Authorization: authToken,
- "content-type": "application/json",
- },
- });
- if (response.ok) {
- if (this.clearReconnectionAttemptMessage) {
- this.clearReconnectionAttemptMessage();
- this.clearReconnectionAttemptMessage = undefined;
+ if (this.checkStatus()) {
+ const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
+ method: "GET",
+ headers: {
+ Authorization: authToken,
+ "content-type": "application/json",
+ },
+ });
+ if (response.ok) {
+ if (this.clearReconnectionAttemptMessage) {
+ this.clearReconnectionAttemptMessage();
+ this.clearReconnectionAttemptMessage = undefined;
+ }
+ const memoryUsageInfo = await response.json();
+ if (memoryUsageInfo) {
+ return {
+ totalKB: memoryUsageInfo.total,
+ freeKB: memoryUsageInfo.free,
+ };
+ }
}
- const memoryUsageInfo = await response.json();
- if (memoryUsageInfo) {
- return {
- totalKB: memoryUsageInfo.total,
- freeKB: memoryUsageInfo.free,
- };
- }
- } else if (NotebookUtil.isPhoenixEnabled()) {
- const connectionStatus: ContainerConnectionInfo = {
- status: ConnectionStatusType.ReConnect,
- };
- useNotebook.getState().resetConatinerConnection(connectionStatus);
- useNotebook.getState().setIsRefreshed(true);
}
return undefined;
} catch (error) {
@@ -97,14 +93,30 @@ export class NotebookContainerClient {
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Failed,
};
- useNotebook.getState().resetConatinerConnection(connectionStatus);
- useNotebook.getState().setIsRefreshed(true);
+ useNotebook.getState().resetContainerConnection(connectionStatus);
+ useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
}
this.onConnectionLost();
return undefined;
}
}
+ private checkStatus(): boolean {
+ if (NotebookUtil.isPhoenixEnabled()) {
+ if (
+ useNotebook.getState().containerStatus?.status &&
+ useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.InActive
+ ) {
+ const connectionStatus: ContainerConnectionInfo = {
+ status: ConnectionStatusType.ReConnect,
+ };
+ useNotebook.getState().resetContainerConnection(connectionStatus);
+ useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
+ return false;
+ }
+ }
+ return true;
+ }
public async resetWorkspace(): Promise {
this.isResettingWorkspace = true;
try {
diff --git a/src/Explorer/Notebook/notebookClientV2.test.ts b/src/Explorer/Notebook/notebookClientV2.test.ts
index 4313932b5..5d3373aef 100644
--- a/src/Explorer/Notebook/notebookClientV2.test.ts
+++ b/src/Explorer/Notebook/notebookClientV2.test.ts
@@ -35,6 +35,7 @@ describe("auto start kernel", () => {
connectionInfo: {
authToken: "autToken",
notebookServerEndpoint: "notebookServerEndpoint",
+ forwardingId: "Id",
},
databaseAccountName: undefined,
defaultExperience: undefined,
diff --git a/src/Explorer/Notebook/useNotebook.ts b/src/Explorer/Notebook/useNotebook.ts
index 3426b56e7..ff9bbe5cd 100644
--- a/src/Explorer/Notebook/useNotebook.ts
+++ b/src/Explorer/Notebook/useNotebook.ts
@@ -7,7 +7,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger";
import { configContext } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
-import { ContainerConnectionInfo } from "../../Contracts/DataModels";
+import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels";
import { IPinnedRepo } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -35,6 +35,7 @@ interface NotebookState {
notebookFolderName: string;
isAllocating: boolean;
isRefreshed: boolean;
+ containerStatus: ContainerInfo;
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
@@ -53,8 +54,9 @@ interface NotebookState {
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
setIsAllocating: (isAllocating: boolean) => void;
- resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
+ resetContainerConnection: (connectionStatus: ContainerConnectionInfo) => void;
setIsRefreshed: (isAllocating: boolean) => void;
+ setContainerStatus: (containerStatus: ContainerInfo) => void;
}
export const useNotebook: UseStore = create((set, get) => ({
@@ -63,6 +65,7 @@ export const useNotebook: UseStore = create((set, get) => ({
notebookServerInfo: {
notebookServerEndpoint: undefined,
authToken: undefined,
+ forwardingId: undefined,
},
sparkClusterConnectionInfo: {
userName: undefined,
@@ -83,6 +86,11 @@ export const useNotebook: UseStore = create((set, get) => ({
notebookFolderName: undefined,
isAllocating: false,
isRefreshed: false,
+ containerStatus: {
+ status: undefined,
+ durationLeftMin: undefined,
+ notebookServerInfo: undefined,
+ },
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
@@ -270,13 +278,20 @@ export const useNotebook: UseStore = create((set, get) => ({
},
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
- resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
+ resetContainerConnection: (connectionStatus: ContainerConnectionInfo): void => {
useNotebook.getState().setConnectionInfo(connectionStatus);
useNotebook.getState().setNotebookServerInfo({
notebookServerEndpoint: undefined,
authToken: undefined,
+ forwardingId: undefined,
});
useNotebook.getState().setIsAllocating(false);
+ useNotebook.getState().setContainerStatus({
+ status: undefined,
+ durationLeftMin: undefined,
+ notebookServerInfo: undefined,
+ });
},
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
+ setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
}));
diff --git a/src/Explorer/Tabs/TerminalTab.tsx b/src/Explorer/Tabs/TerminalTab.tsx
index 7eee433d3..6f318f3cf 100644
--- a/src/Explorer/Tabs/TerminalTab.tsx
+++ b/src/Explorer/Tabs/TerminalTab.tsx
@@ -100,6 +100,7 @@ export default class TerminalTab extends TabsBase {
return {
authToken: info.authToken,
notebookServerEndpoint: `${info.notebookServerEndpoint.replace(/\/+$/, "")}/${endpointSuffix}`,
+ forwardingId: info.forwardingId,
};
}
}
diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts
index bb7797afd..6f4a656b0 100644
--- a/src/Phoenix/PhoenixClient.ts
+++ b/src/Phoenix/PhoenixClient.ts
@@ -1,5 +1,9 @@
-import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
+import { ContainerStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../Common/Constants";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
+import * as Logger from "../Common/Logger";
import { configContext } from "../ConfigContext";
+import { ContainerInfo } from "../Contracts/DataModels";
+import { useNotebook } from "../Explorer/Notebook/useNotebook";
import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
@@ -10,17 +14,24 @@ export interface IPhoenixResponse {
export interface IPhoenixConnectionInfoResult {
readonly notebookAuthToken?: string;
readonly notebookServerUrl?: string;
+ readonly forwardingId?: string;
}
-export interface IProvosionData {
+export interface IProvisionData {
cosmosEndpoint: string;
dbAccountName: string;
aadToken: string;
resourceGroup: string;
subscriptionId: string;
}
+
+export interface IContainerData {
+ dbAccountName: string;
+ forwardingId: string;
+}
+
export class PhoenixClient {
public async containerConnectionInfo(
- provisionData: IProvosionData
+ provisionData: IProvisionData
): Promise> {
try {
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/allocate`, {
@@ -41,6 +52,60 @@ export class PhoenixClient {
throw error;
}
}
+ public async initiateContainerHeartBeat(containerData: { forwardingId: string; dbAccountName: string }) {
+ this.getContainerHealth(Notebook.containerStatusHeartbeatDelayMs, containerData);
+ }
+
+ private scheduleContainerHeartbeat(delayMs: number, containerData: IContainerData): void {
+ setTimeout(() => {
+ this.getContainerHealth(delayMs, containerData);
+ }, delayMs);
+ }
+
+ private async getContainerStatusAsync(containerData: IContainerData): Promise {
+ try {
+ const response = await window.fetch(
+ `${this.getPhoenixContainerPoolingEndPoint()}/${containerData.dbAccountName}/${containerData.forwardingId}`,
+ {
+ method: "GET",
+ headers: PhoenixClient.getHeaders(),
+ }
+ );
+ if (response.status === HttpStatusCodes.OK) {
+ const containerStatus = await response.json();
+ return {
+ durationLeftMin: containerStatus.durationLeftInMinutes,
+ notebookServerInfo: containerStatus.notebookServerInfo,
+ status: ContainerStatusType.Active,
+ };
+ }
+ return {
+ durationLeftMin: undefined,
+ notebookServerInfo: undefined,
+ status: ContainerStatusType.InActive,
+ };
+ } catch (error) {
+ Logger.logError(getErrorMessage(error), "PhoenixClient/getContainerStatus");
+ return {
+ durationLeftMin: undefined,
+ notebookServerInfo: undefined,
+ status: ContainerStatusType.InActive,
+ };
+ }
+ }
+
+ private getContainerHealth(delayMs: number, containerData: { forwardingId: string; dbAccountName: string }) {
+ this.getContainerStatusAsync(containerData)
+ .then((ContainerInfo) => useNotebook.getState().setContainerStatus(ContainerInfo))
+ .finally(() => {
+ if (
+ useNotebook.getState().containerStatus.status &&
+ useNotebook.getState().containerStatus.status === ContainerStatusType.Active
+ ) {
+ this.scheduleContainerHeartbeat(delayMs, containerData);
+ }
+ });
+ }
public static getPhoenixEndpoint(): string {
const phoenixEndpoint =
diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts
index fb5b7dcb8..060b24f3f 100644
--- a/src/Utils/GalleryUtils.ts
+++ b/src/Utils/GalleryUtils.ts
@@ -239,7 +239,7 @@ export function downloadItem(
useDialog
.getState()
.showOkModalDialog(
- "Failed to Connect",
+ "Failed to connect",
"Failed to connect to temporary workspace. Please refresh the page and try again."
);
}