mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-18 02:07:04 +00:00
Allocation of container on demand (#1097)
* Allocation of container only on demand * git notebook click emits connect to container and refresh notebooks * git cleanup local repo * Reconnect rename and memory heartbeat change * Fix new notebook click event in case of git login * Mongo proxy test replace with master file * code refactor * format check * compile errors resolved * address review comments * rename environment to workspace * Added content html rendering in Dialog * format issue * Added contentHtml in the dialog which renders jsx element
This commit is contained in:
parent
6ca8e3c6f4
commit
f968f57543
@ -339,9 +339,11 @@ export enum ConflictOperationType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ConnectionStatusType {
|
export enum ConnectionStatusType {
|
||||||
|
Connect = "Connect",
|
||||||
Connecting = "Connecting",
|
Connecting = "Connecting",
|
||||||
Connected = "Connected",
|
Connected = "Connected",
|
||||||
Failed = "Connection Failed",
|
Failed = "Connection Failed",
|
||||||
|
ReConnect = "Reconnect",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
@ -353,15 +355,32 @@ export const StyleConstants = require("less-vars-loader!../../less/Common/Consta
|
|||||||
|
|
||||||
export class Notebook {
|
export class Notebook {
|
||||||
public static readonly defaultBasePath = "./notebooks";
|
public static readonly defaultBasePath = "./notebooks";
|
||||||
public static readonly heartbeatDelayMs = 5000;
|
public static readonly heartbeatDelayMs = 60000;
|
||||||
public static readonly kernelRestartInitialDelayMs = 1000;
|
public static readonly kernelRestartInitialDelayMs = 1000;
|
||||||
public static readonly kernelRestartMaxDelayMs = 20000;
|
public static readonly kernelRestartMaxDelayMs = 20000;
|
||||||
public static readonly autoSaveIntervalMs = 120000;
|
public static readonly autoSaveIntervalMs = 120000;
|
||||||
|
public static readonly memoryGuageToGB = 1048576;
|
||||||
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
|
||||||
public static readonly mongoShellTemporarilyDownMsg =
|
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.";
|
"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 =
|
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.";
|
"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 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 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 =
|
||||||
|
"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 galleryNotebookDownloadContent1 =
|
||||||
|
"To download, run, and make changes to this sample notebook, a temporary workspace will be created. When the session expires, any notebooks in the workspace will be removed.";
|
||||||
|
public static galleryNotebookDownloadContent2 =
|
||||||
|
"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 cosmosNotebookHomePageUrl = "https://aka.ms/cosmos-notebooks-limits";
|
||||||
|
public static cosmosNotebookGitDocumentationUrl = "https://aka.ms/cosmos-notebooks-github";
|
||||||
|
public static learnMore = "Learn more.";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SparkLibrary {
|
export class SparkLibrary {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
ProgressIndicator,
|
ProgressIndicator,
|
||||||
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
@ -30,6 +31,7 @@ export interface DialogState {
|
|||||||
onOk: () => void,
|
onOk: () => void,
|
||||||
cancelLabel: string,
|
cancelLabel: string,
|
||||||
onCancel: () => void,
|
onCancel: () => void,
|
||||||
|
contentHtml?: JSX.Element,
|
||||||
choiceGroupProps?: IChoiceGroupProps,
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
textFieldProps?: TextFieldProps,
|
textFieldProps?: TextFieldProps,
|
||||||
primaryButtonDisabled?: boolean
|
primaryButtonDisabled?: boolean
|
||||||
@ -58,6 +60,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
|||||||
onOk: () => void,
|
onOk: () => void,
|
||||||
cancelLabel: string,
|
cancelLabel: string,
|
||||||
onCancel: () => void,
|
onCancel: () => void,
|
||||||
|
contentHtml?: JSX.Element,
|
||||||
choiceGroupProps?: IChoiceGroupProps,
|
choiceGroupProps?: IChoiceGroupProps,
|
||||||
textFieldProps?: TextFieldProps,
|
textFieldProps?: TextFieldProps,
|
||||||
primaryButtonDisabled?: boolean
|
primaryButtonDisabled?: boolean
|
||||||
@ -76,6 +79,7 @@ export const useDialog: UseStore<DialogState> = create((set, get) => ({
|
|||||||
get().closeDialog();
|
get().closeDialog();
|
||||||
onCancel && onCancel();
|
onCancel && onCancel();
|
||||||
},
|
},
|
||||||
|
contentHtml,
|
||||||
choiceGroupProps,
|
choiceGroupProps,
|
||||||
textFieldProps,
|
textFieldProps,
|
||||||
primaryButtonDisabled,
|
primaryButtonDisabled,
|
||||||
@ -124,6 +128,7 @@ export interface DialogProps {
|
|||||||
type?: DialogType;
|
type?: DialogType;
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
|
contentHtml?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DIALOG_MIN_WIDTH = "400px";
|
const DIALOG_MIN_WIDTH = "400px";
|
||||||
@ -150,6 +155,7 @@ export const Dialog: FC = () => {
|
|||||||
type,
|
type,
|
||||||
showCloseButton,
|
showCloseButton,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
|
contentHtml,
|
||||||
} = props || {};
|
} = props || {};
|
||||||
|
|
||||||
const dialogProps: IDialogProps = {
|
const dialogProps: IDialogProps = {
|
||||||
@ -191,6 +197,7 @@ export const Dialog: FC = () => {
|
|||||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
{contentHtml && <Text>{contentHtml}</Text>}
|
||||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton {...primaryButtonProps} />
|
<PrimaryButton {...primaryButtonProps} />
|
||||||
|
@ -17,6 +17,8 @@ import Explorer from "../../Explorer";
|
|||||||
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
|
||||||
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
|
||||||
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
import { Dialog, TextFieldProps, useDialog } from "../Dialog";
|
||||||
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||||
import "./NotebookViewerComponent.less";
|
import "./NotebookViewerComponent.less";
|
||||||
@ -146,7 +148,9 @@ export class NotebookViewerComponent
|
|||||||
<NotebookMetadataComponent
|
<NotebookMetadataComponent
|
||||||
data={this.state.galleryItem}
|
data={this.state.galleryItem}
|
||||||
isFavorite={this.state.isFavorite}
|
isFavorite={this.state.isFavorite}
|
||||||
downloadButtonText={this.props.container && "Download to my notebooks"}
|
downloadButtonText={
|
||||||
|
this.props.container && NotebookUtil.getNotebookBtnTitle(useNotebook.getState().notebookFolderName)
|
||||||
|
}
|
||||||
onTagClick={this.props.onTagClick}
|
onTagClick={this.props.onTagClick}
|
||||||
onFavoriteClick={this.favoriteItem}
|
onFavoriteClick={this.favoriteItem}
|
||||||
onUnfavoriteClick={this.unfavoriteItem}
|
onUnfavoriteClick={this.unfavoriteItem}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { Link } from "@fluentui/react/lib/Link";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
|
import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
|
||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||||
@ -11,6 +13,7 @@ import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHand
|
|||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { QueriesClient } from "../Common/QueriesClient";
|
import { QueriesClient } from "../Common/QueriesClient";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
@ -163,23 +166,10 @@ export default class Explorer {
|
|||||||
|
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
async () => {
|
async () => {
|
||||||
if (!this.notebookManager) {
|
this.initiateAndRefreshNotebookList();
|
||||||
const NotebookManager = await (
|
useNotebook.getState().setIsRefreshed(false);
|
||||||
await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager")
|
|
||||||
).default;
|
|
||||||
this.notebookManager = new NotebookManager();
|
|
||||||
this.notebookManager.initialize({
|
|
||||||
container: this,
|
|
||||||
resourceTree: this.resourceTree,
|
|
||||||
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
|
|
||||||
refreshNotebookList: () => this.refreshNotebookList(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.refreshCommandBarButtons();
|
|
||||||
this.refreshNotebookList();
|
|
||||||
},
|
},
|
||||||
(state) => state.isNotebookEnabled
|
(state) => state.isNotebookEnabled || state.isRefreshed
|
||||||
);
|
);
|
||||||
|
|
||||||
this.resourceTree = new ResourceTreeAdapter(this);
|
this.resourceTree = new ResourceTreeAdapter(this);
|
||||||
@ -212,6 +202,23 @@ export default class Explorer {
|
|||||||
this.refreshExplorer();
|
this.refreshExplorer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async initiateAndRefreshNotebookList(): Promise<void> {
|
||||||
|
if (!this.notebookManager) {
|
||||||
|
const NotebookManager = (await import(/* webpackChunkName: "NotebookManager" */ "./Notebook/NotebookManager"))
|
||||||
|
.default;
|
||||||
|
this.notebookManager = new NotebookManager();
|
||||||
|
this.notebookManager.initialize({
|
||||||
|
container: this,
|
||||||
|
resourceTree: this.resourceTree,
|
||||||
|
refreshCommandBarButtons: () => this.refreshCommandBarButtons(),
|
||||||
|
refreshNotebookList: () => this.refreshNotebookList(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshCommandBarButtons();
|
||||||
|
this.refreshNotebookList();
|
||||||
|
}
|
||||||
|
|
||||||
public openEnableSynapseLinkDialog(): void {
|
public openEnableSynapseLinkDialog(): void {
|
||||||
const addSynapseLinkDialogProps: DialogProps = {
|
const addSynapseLinkDialogProps: DialogProps = {
|
||||||
linkProps: {
|
linkProps: {
|
||||||
@ -345,23 +352,7 @@ export default class Explorer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
if (userContext.features.phoenix) {
|
if (userContext.features.phoenix === false) {
|
||||||
const provisionData = {
|
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
|
||||||
resourceId: userContext.databaseAccount.id,
|
|
||||||
dbAccountName: userContext.databaseAccount.name,
|
|
||||||
aadToken: userContext.authorizationToken,
|
|
||||||
resourceGroup: userContext.resourceGroup,
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
};
|
|
||||||
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
|
||||||
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.ensureNotebookWorkspaceRunning();
|
await this.ensureNotebookWorkspaceRunning();
|
||||||
const connectionInfo = await listConnectionInfo(
|
const connectionInfo = await listConnectionInfo(
|
||||||
userContext.subscriptionId,
|
userContext.subscriptionId,
|
||||||
@ -376,13 +367,59 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
|
||||||
|
|
||||||
this.refreshNotebookList();
|
this.refreshNotebookList();
|
||||||
|
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async allocateContainer(): Promise<void> {
|
||||||
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
|
const isAllocating = useNotebook.getState().isAllocating;
|
||||||
|
if (isAllocating === false && notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined) {
|
||||||
|
const provisionData = {
|
||||||
|
aadToken: userContext.authorizationToken,
|
||||||
|
subscriptionId: userContext.subscriptionId,
|
||||||
|
resourceGroup: userContext.resourceGroup,
|
||||||
|
dbAccountName: userContext.databaseAccount.name,
|
||||||
|
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
||||||
|
};
|
||||||
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.Connecting,
|
||||||
|
};
|
||||||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
|
try {
|
||||||
|
useNotebook.getState().setIsAllocating(true);
|
||||||
|
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
||||||
|
if (
|
||||||
|
connectionInfo.status === HttpStatusCodes.OK &&
|
||||||
|
connectionInfo.data &&
|
||||||
|
connectionInfo.data.notebookServerUrl
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
this.notebookManager?.notebookClient
|
||||||
|
.getMemoryUsage()
|
||||||
|
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
|
||||||
|
useNotebook.getState().setIsAllocating(false);
|
||||||
|
} else {
|
||||||
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
this.refreshNotebookList();
|
||||||
|
|
||||||
|
this._isInitializingNotebooks = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public resetNotebookWorkspace(): void {
|
public resetNotebookWorkspace(): void {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||||
handleError(
|
handleError(
|
||||||
@ -654,6 +691,9 @@ export default class Explorer {
|
|||||||
if (!notebookContentItem || !notebookContentItem.path) {
|
if (!notebookContentItem || !notebookContentItem.path) {
|
||||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||||
}
|
}
|
||||||
|
if (notebookContentItem.type === NotebookContentItemType.Notebook && NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
this.allocateContainer();
|
||||||
|
}
|
||||||
|
|
||||||
const notebookTabs = useTabs
|
const notebookTabs = useTabs
|
||||||
.getState()
|
.getState()
|
||||||
@ -875,9 +915,51 @@ export default class Explorer {
|
|||||||
handleError(error, "Explorer/onNewNotebookClicked");
|
handleError(error, "Explorer/onNewNotebookClicked");
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
const isPhoenixEnabled = NotebookUtil.isPhoenixEnabled();
|
||||||
|
if (isPhoenixEnabled) {
|
||||||
|
if (isGithubTree) {
|
||||||
|
async () => {
|
||||||
|
await this.allocateContainer();
|
||||||
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.createNewNoteBook(parent, isGithubTree);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
|
Notebook.newNotebookModalTitle,
|
||||||
|
undefined,
|
||||||
|
"Create",
|
||||||
|
async () => {
|
||||||
|
await this.allocateContainer();
|
||||||
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.createNewNoteBook(parent, isGithubTree);
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
undefined,
|
||||||
|
this.getNewNoteWarningText()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.createNewNoteBook(parent, isGithubTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
private getNewNoteWarningText(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>{Notebook.newNotebookModalContent1}</p>
|
||||||
|
<br />
|
||||||
|
<p>
|
||||||
|
{Notebook.newNotebookModalContent2}
|
||||||
|
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
|
||||||
|
{Notebook.learnMore}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void {
|
||||||
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
|
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
|
||||||
dataExplorerArea: Constants.Areas.Notebook,
|
dataExplorerArea: Constants.Areas.Notebook,
|
||||||
@ -924,7 +1006,26 @@ export default class Explorer {
|
|||||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||||
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
await this.allocateContainer();
|
||||||
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
|
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||||
|
this.connectToNotebookTerminal(kind);
|
||||||
|
} else {
|
||||||
|
useDialog
|
||||||
|
.getState()
|
||||||
|
.showOkModalDialog(
|
||||||
|
"Failed to Connect",
|
||||||
|
"Failed to connect temporary workspace, this could happen because of network issue please refresh and try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.connectToNotebookTerminal(kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private connectToNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
||||||
let title: string;
|
let title: string;
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
@ -975,7 +1076,7 @@ export default class Explorer {
|
|||||||
notebookUrl?: string,
|
notebookUrl?: string,
|
||||||
galleryItem?: IGalleryItem,
|
galleryItem?: IGalleryItem,
|
||||||
isFavorite?: boolean
|
isFavorite?: boolean
|
||||||
) {
|
): Promise<void> {
|
||||||
const title = "Gallery";
|
const title = "Gallery";
|
||||||
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
||||||
const galleryTab = useTabs
|
const galleryTab = useTabs
|
||||||
@ -1079,7 +1180,27 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
|
Notebook.newNotebookUploadModalTitle,
|
||||||
|
undefined,
|
||||||
|
"Upload",
|
||||||
|
async () => {
|
||||||
|
await this.allocateContainer();
|
||||||
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.uploadFilePanel(parent);
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
undefined,
|
||||||
|
this.getNewNoteWarningText()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
this.uploadFilePanel(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private uploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel(
|
.openSidePanel(
|
||||||
@ -1088,6 +1209,24 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDownloadModalConent(fileName: string): JSX.Element {
|
||||||
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>{Notebook.galleryNotebookDownloadContent1}</p>
|
||||||
|
<br />
|
||||||
|
<p>
|
||||||
|
{Notebook.galleryNotebookDownloadContent2}
|
||||||
|
<Link href={Notebook.cosmosNotebookGitDocumentationUrl} target="_blank">
|
||||||
|
{Notebook.learnMore}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <p> Download {fileName} from gallery as a copy to your notebooks to run and/or edit the notebook. </p>;
|
||||||
|
}
|
||||||
|
|
||||||
public async refreshExplorer(): Promise<void> {
|
public async refreshExplorer(): Promise<void> {
|
||||||
userContext.authType === AuthType.ResourceToken
|
userContext.authType === AuthType.ResourceToken
|
||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
|
@ -12,6 +12,7 @@ import { useTabs } from "../../../hooks/useTabs";
|
|||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { NotebookUtil } from "../../Notebook/NotebookUtil";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
import * as CommandBarUtil from "./CommandBarUtil";
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
@ -55,15 +56,15 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
if (
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
userContext.features.notebooksTemporarilyDown === false &&
|
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||||
userContext.features.phoenix === true &&
|
|
||||||
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
|
||||||
) {
|
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
if (
|
||||||
|
userContext.features.phoenix === false &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||||
|
) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,7 +596,7 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
|||||||
return {
|
return {
|
||||||
iconSrc: GitHubIcon,
|
iconSrc: GitHubIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () =>
|
onCommandClick: () => {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel(
|
.openSidePanel(
|
||||||
@ -606,7 +606,8 @@ function createManageGitHubAccountButton(container: Explorer): CommandButtonComp
|
|||||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
gitHubClientProp={container.notebookManager.gitHubClient}
|
||||||
junoClientProp={junoClient}
|
junoClientProp={junoClient}
|
||||||
/>
|
/>
|
||||||
),
|
);
|
||||||
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
@ -13,6 +13,7 @@ import { StyleConstants } from "../../../Common/Constants";
|
|||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
||||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||||
|
|
||||||
@ -203,9 +204,9 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createConnectionStatus = (key: string): ICommandBarItemProps => {
|
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
onRender: () => <ConnectionStatus />,
|
onRender: () => <ConnectionStatus container={container} />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -3,77 +3,182 @@
|
|||||||
.connectionStatusContainer {
|
.connectionStatusContainer {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 9px;
|
|
||||||
border: 1px;
|
border: 1px;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
font-family: @DataExplorerFont;
|
font-family: @DataExplorerFont;
|
||||||
color: @DefaultFontColor;
|
color: @DefaultFontColor;
|
||||||
}
|
}
|
||||||
|
&:focus{
|
||||||
|
outline: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.connectionStatusFailed{
|
.commandReactBtn {
|
||||||
color: #bd1919;
|
&:hover {
|
||||||
|
background-color: rgb(238, 247, 255);
|
||||||
|
color: rgb(32, 31, 30);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&:focus{
|
||||||
|
outline: 1px dashed #605e5c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ring-container {
|
.connectedReactBtn {
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(238, 247, 255);
|
||||||
|
color: rgb(32, 31, 30);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&:focus{
|
||||||
|
outline: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.connectIcon{
|
||||||
|
margin: 0px 4px;
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
color: rgb(0, 120, 212);
|
||||||
|
}
|
||||||
|
.status {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
display: block;
|
||||||
|
margin-right: 8px;
|
||||||
.ringringGreen {
|
width: 1em;
|
||||||
border: 3px solid green;
|
height: 1em;
|
||||||
border-radius: 30px;
|
font-size: 9px!important;
|
||||||
height: 18px;
|
padding: 0px!important;
|
||||||
width: 18px;
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status::before,
|
||||||
|
.status::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: .4285em 0em 0em 0.07477em;
|
content: "";
|
||||||
animation: pulsate 3s ease-out;
|
}
|
||||||
animation-iteration-count: infinite;
|
|
||||||
opacity: 0.0
|
.status::before {
|
||||||
}
|
top: 0;
|
||||||
.ringringYellow{
|
left: 0;
|
||||||
border: 3px solid #ffbf00;
|
width: 1em;
|
||||||
border-radius: 30px;
|
height: 1em;
|
||||||
height: 18px;
|
background-color: rgba(#fff, 0.1);
|
||||||
width: 18px;
|
border-radius: 100%;
|
||||||
position: absolute;
|
opacity: 1;
|
||||||
margin: .4285em 0em 0em 0.07477em;
|
transform: translate3d(0, 0, 0) scale(0);
|
||||||
animation: pulsate 3s ease-out;
|
}
|
||||||
animation-iteration-count: infinite;
|
|
||||||
opacity: 0.0
|
.connected{
|
||||||
}
|
background-color: green;
|
||||||
.ringringRed{
|
box-shadow:
|
||||||
border: 3px solid #bd1919;
|
0 0 0 0em rgba(green, 0),
|
||||||
border-radius: 30px;
|
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||||
height: 18px;
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
width: 18px;
|
}
|
||||||
position: absolute;
|
.connecting{
|
||||||
margin: .4285em 0em 0em 0.07477em;
|
background-color:#ffbf00;
|
||||||
animation: pulsate 3s ease-out;
|
box-shadow:
|
||||||
animation-iteration-count: infinite;
|
0 0 0 0em rgba(#ffbf00, 0),
|
||||||
opacity: 0.0
|
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||||
}
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
@keyframes pulsate {
|
}
|
||||||
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;}
|
.failed{
|
||||||
15% {opacity: 0.8;}
|
background-color:#bd1919;
|
||||||
25% {opacity: 0.6;}
|
box-shadow:
|
||||||
45% {opacity: 0.4;}
|
0 0 0 0em rgba(#bd1919, 0),
|
||||||
70% {opacity: 0.3;}
|
0em 0.05em 0.1em rgba(#000000, 0.2);
|
||||||
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;}
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
}
|
}
|
||||||
.locationGreenDot{
|
|
||||||
font-size: 20px;
|
.status.connecting.is-animating {
|
||||||
margin-right: 0.07em;
|
animation: status-outer-connecting 3000ms infinite;
|
||||||
color: green;
|
}
|
||||||
}
|
.status.failed.is-animating {
|
||||||
.locationYellowDot{
|
animation: status-outer-failed 3000ms infinite;
|
||||||
font-size: 20px;
|
}
|
||||||
margin-right: 0.07em;
|
.status.connected.is-animating {
|
||||||
color: #ffbf00;
|
animation: status-outer-connected 3000ms infinite;
|
||||||
}
|
}
|
||||||
.locationRedDot{
|
@keyframes status-outer-connected {
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 0.07em;
|
0% {
|
||||||
color: #bd1919;
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
}
|
box-shadow: 0 0 0 0em #008000, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.6), 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
85% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes status-outer-failed {
|
||||||
|
|
||||||
|
0% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em #bd1919, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em #c52d2d, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em #b47b7b, 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
85% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes status-outer-connecting {
|
||||||
|
|
||||||
|
0% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em #ffbf00, 0em 0.05em 0.1em rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em #f0dfad, 0em 0.05em 0.1em rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(198, 243, 198, 0.5), 0em 0.05em 0.1em rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(213, 241, 213, 0.3), 0em 0.05em 0.1em rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0.5em rgba(0, 128, 0, 0.1), 0em 0.05em 0.1em rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
85% {
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
box-shadow: 0 0 0 0em rgba(0, 128, 0, 0), 0em 0.05em 0.1em rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,21 @@
|
|||||||
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
import { Icon, ProgressIndicator, Stack, TooltipHost } from "@fluentui/react";
|
||||||
|
import { ActionButton } from "@fluentui/react/lib/Button";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
import "../../../../less/hostedexplorer.less";
|
||||||
|
import { ConnectionStatusType, Notebook } from "../../../Common/Constants";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
import { useNotebook } from "../../Notebook/useNotebook";
|
||||||
import "../CommandBar/ConnectionStatusComponent.less";
|
import "../CommandBar/ConnectionStatusComponent.less";
|
||||||
|
interface Props {
|
||||||
export const ConnectionStatus: React.FC = (): JSX.Element => {
|
container: Explorer;
|
||||||
|
}
|
||||||
|
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||||
const [second, setSecond] = React.useState("00");
|
const [second, setSecond] = React.useState("00");
|
||||||
const [minute, setMinute] = React.useState("00");
|
const [minute, setMinute] = React.useState("00");
|
||||||
const [isActive, setIsActive] = React.useState(false);
|
const [isActive, setIsActive] = React.useState(false);
|
||||||
const [counter, setCounter] = React.useState(0);
|
const [counter, setCounter] = React.useState(0);
|
||||||
const [statusColor, setStatusColor] = React.useState("locationYellowDot");
|
const [statusColor, setStatusColor] = React.useState("");
|
||||||
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow");
|
const [toolTipContent, setToolTipContent] = React.useState("Connect to temporary workspace.");
|
||||||
const toolTipContent = "Hosted runtime status.";
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let intervalId: NodeJS.Timeout;
|
let intervalId: NodeJS.Timeout;
|
||||||
|
|
||||||
@ -39,34 +43,65 @@ export const ConnectionStatus: React.FC = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||||
if (!connectionInfo) {
|
const memoryUsageInfo = useNotebook((state) => state.memoryUsageInfo);
|
||||||
return <></>;
|
|
||||||
|
const totalGB = memoryUsageInfo ? memoryUsageInfo.totalKB / Notebook.memoryGuageToGB : 0;
|
||||||
|
const usedGB = totalGB > 0 ? totalGB - memoryUsageInfo.freeKB / Notebook.memoryGuageToGB : 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
connectionInfo &&
|
||||||
|
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.ReConnect)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||||
|
<TooltipHost content={toolTipContent}>
|
||||||
|
<Stack className="connectionStatusContainer" horizontal>
|
||||||
|
<Icon iconName="ConnectVirtualMachine" className="connectIcon" />
|
||||||
|
<span>{connectionInfo.status}</span>
|
||||||
|
</Stack>
|
||||||
|
</TooltipHost>
|
||||||
|
</ActionButton>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connecting && isActive === false) {
|
||||||
setIsActive(true);
|
setIsActive(true);
|
||||||
|
setStatusColor("status connecting is-animating");
|
||||||
|
setToolTipContent("Connecting to temporary workspace.");
|
||||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
||||||
stopTimer();
|
stopTimer();
|
||||||
setStatusColor("locationGreenDot");
|
setStatusColor("status connected is-animating");
|
||||||
setStatusColorAnimation("ringringGreen");
|
setToolTipContent("Connected to temporary workspace.");
|
||||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
||||||
stopTimer();
|
stopTimer();
|
||||||
setStatusColor("locationRedDot");
|
setStatusColor("status failed is-animating");
|
||||||
setStatusColorAnimation("ringringRed");
|
setToolTipContent("Click here to Reconnect to temporary workspace.");
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TooltipHost content={toolTipContent}>
|
<ActionButton
|
||||||
<Stack className="connectionStatusContainer" horizontal>
|
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||||
<div className="ring-container">
|
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||||
<div className={statusColorAnimation}></div>
|
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||||
<Icon iconName="LocationDot" className={statusColor} />
|
}
|
||||||
</div>
|
>
|
||||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
<TooltipHost content={toolTipContent}>
|
||||||
{connectionInfo.status}
|
<Stack className="connectionStatusContainer" horizontal>
|
||||||
</span>
|
<i className={statusColor}></i>
|
||||||
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
||||||
<ProgressIndicator description={minute + ":" + second} />
|
{connectionInfo.status}
|
||||||
)}
|
</span>
|
||||||
</Stack>
|
{connectionInfo.status === ConnectionStatusType.Connecting && isActive && (
|
||||||
</TooltipHost>
|
<ProgressIndicator description={minute + ":" + second} />
|
||||||
|
)}
|
||||||
|
{connectionInfo.status === ConnectionStatusType.Connected && !isActive && (
|
||||||
|
<ProgressIndicator
|
||||||
|
className={usedGB / totalGB > 0.8 ? "lowMemory" : ""}
|
||||||
|
description={usedGB.toFixed(1) + " of " + totalGB.toFixed(1) + " GB"}
|
||||||
|
percentComplete={usedGB / totalGB}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</TooltipHost>
|
||||||
|
</ActionButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { ConnectionStatusType } from "../../Common/Constants";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
import { useNotebook } from "./useNotebook";
|
import { useNotebook } from "./useNotebook";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
@ -42,7 +45,7 @@ export class NotebookContainerClient {
|
|||||||
}, delayMs);
|
}, delayMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
|
||||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
|
||||||
const error = "No server endpoint detected";
|
const error = "No server endpoint detected";
|
||||||
@ -75,6 +78,12 @@ export class NotebookContainerClient {
|
|||||||
freeKB: memoryUsageInfo.free,
|
freeKB: memoryUsageInfo.free,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} else if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.ReConnect,
|
||||||
|
};
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
useNotebook.getState().setIsRefreshed(true);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -84,6 +93,13 @@ export class NotebookContainerClient {
|
|||||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
|
const connectionStatus: ContainerConnectionInfo = {
|
||||||
|
status: ConnectionStatusType.Failed,
|
||||||
|
};
|
||||||
|
useNotebook.getState().resetConatinerConnection(connectionStatus);
|
||||||
|
useNotebook.getState().setIsRefreshed(true);
|
||||||
|
}
|
||||||
this.onConnectionLost();
|
this.onConnectionLost();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,7 @@ export default class NotebookManager {
|
|||||||
"Cancel",
|
"Cancel",
|
||||||
() => reject(new Error("Commit dialog canceled")),
|
() => reject(new Error("Commit dialog canceled")),
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
{
|
{
|
||||||
label: "Commit message",
|
label: "Commit message",
|
||||||
autoAdjustHeight: true,
|
autoAdjustHeight: true,
|
||||||
|
@ -3,6 +3,7 @@ import { AppState, selectors } from "@nteract/core";
|
|||||||
import domtoimage from "dom-to-image";
|
import domtoimage from "dom-to-image";
|
||||||
import Html2Canvas from "html2canvas";
|
import Html2Canvas from "html2canvas";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import * as StringUtils from "../../Utils/StringUtils";
|
import * as StringUtils from "../../Utils/StringUtils";
|
||||||
import { SnapshotFragment } from "./NotebookComponent/types";
|
import { SnapshotFragment } from "./NotebookComponent/types";
|
||||||
@ -328,4 +329,16 @@ export class NotebookUtil {
|
|||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ import { cloneDeep } from "lodash";
|
|||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { ConnectionStatusType } from "../../Common/Constants";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { ContainerConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@ -14,6 +16,7 @@ import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
|||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
|
||||||
import NotebookManager from "./NotebookManager";
|
import NotebookManager from "./NotebookManager";
|
||||||
|
import { NotebookUtil } from "./NotebookUtil";
|
||||||
|
|
||||||
interface NotebookState {
|
interface NotebookState {
|
||||||
isNotebookEnabled: boolean;
|
isNotebookEnabled: boolean;
|
||||||
@ -28,8 +31,10 @@ interface NotebookState {
|
|||||||
myNotebooksContentRoot: NotebookContentItem;
|
myNotebooksContentRoot: NotebookContentItem;
|
||||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||||
galleryContentRoot: NotebookContentItem;
|
galleryContentRoot: NotebookContentItem;
|
||||||
connectionInfo: DataModels.ContainerConnectionInfo;
|
connectionInfo: ContainerConnectionInfo;
|
||||||
notebookFolderName: string;
|
notebookFolderName: string;
|
||||||
|
isAllocating: boolean;
|
||||||
|
isRefreshed: boolean;
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
@ -46,7 +51,10 @@ interface NotebookState {
|
|||||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void;
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => void;
|
||||||
|
setIsAllocating: (isAllocating: boolean) => void;
|
||||||
|
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo) => void;
|
||||||
|
setIsRefreshed: (isAllocating: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
@ -69,8 +77,12 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
myNotebooksContentRoot: undefined,
|
myNotebooksContentRoot: undefined,
|
||||||
gitHubNotebooksContentRoot: undefined,
|
gitHubNotebooksContentRoot: undefined,
|
||||||
galleryContentRoot: undefined,
|
galleryContentRoot: undefined,
|
||||||
connectionInfo: undefined,
|
connectionInfo: {
|
||||||
|
status: ConnectionStatusType.Connect,
|
||||||
|
},
|
||||||
notebookFolderName: undefined,
|
notebookFolderName: undefined,
|
||||||
|
isAllocating: false,
|
||||||
|
isRefreshed: false,
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
@ -175,7 +187,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
|
||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const notebookFolderName = userContext.features.phoenix === true ? "Temporary Notebooks" : "My Notebooks";
|
const notebookFolderName = NotebookUtil.isPhoenixEnabled() === true ? "Temporary Notebooks" : "My Notebooks";
|
||||||
set({ notebookFolderName });
|
set({ notebookFolderName });
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().notebookFolderName,
|
name: get().notebookFolderName,
|
||||||
@ -256,5 +268,15 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
set({ gitHubNotebooksContentRoot });
|
set({ gitHubNotebooksContentRoot });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }),
|
setConnectionInfo: (connectionInfo: ContainerConnectionInfo) => set({ connectionInfo }),
|
||||||
|
setIsAllocating: (isAllocating: boolean) => set({ isAllocating }),
|
||||||
|
resetConatinerConnection: (connectionStatus: ContainerConnectionInfo): void => {
|
||||||
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
|
notebookServerEndpoint: undefined,
|
||||||
|
authToken: undefined,
|
||||||
|
});
|
||||||
|
useNotebook.getState().setIsAllocating(false);
|
||||||
|
},
|
||||||
|
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
|
||||||
}));
|
}));
|
||||||
|
@ -77,7 +77,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
selectedLocation.repo
|
selectedLocation.repo
|
||||||
)} - ${selectedLocation.branch}`;
|
)} - ${selectedLocation.branch}`;
|
||||||
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
||||||
destination = "My Notebooks Scratch";
|
destination = useNotebook.getState().notebookFolderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||||
|
@ -128,7 +128,12 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
notebooksTree.children.push(buildGalleryNotebooksTree());
|
notebooksTree.children.push(buildGalleryNotebooksTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myNotebooksContentRoot && useNotebook.getState().connectionInfo.status == ConnectionStatusType.Connected) {
|
if (
|
||||||
|
myNotebooksContentRoot &&
|
||||||
|
((NotebookUtil.isPhoenixEnabled() &&
|
||||||
|
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected) ||
|
||||||
|
userContext.features.phoenix === false)
|
||||||
|
) {
|
||||||
notebooksTree.children.push(buildMyNotebooksTree());
|
notebooksTree.children.push(buildMyNotebooksTree());
|
||||||
}
|
}
|
||||||
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||||
@ -162,7 +167,11 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
myNotebooksContentRoot,
|
myNotebooksContentRoot,
|
||||||
(item: NotebookContentItem) => {
|
(item: NotebookContentItem) => {
|
||||||
container.openNotebook(item).then((hasOpened) => {
|
container.openNotebook(item).then((hasOpened) => {
|
||||||
if (hasOpened) {
|
if (
|
||||||
|
hasOpened &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
userContext.features.phoenix === false
|
||||||
|
) {
|
||||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -181,7 +190,11 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
gitHubNotebooksContentRoot,
|
gitHubNotebooksContentRoot,
|
||||||
(item: NotebookContentItem) => {
|
(item: NotebookContentItem) => {
|
||||||
container.openNotebook(item).then((hasOpened) => {
|
container.openNotebook(item).then((hasOpened) => {
|
||||||
if (hasOpened) {
|
if (
|
||||||
|
hasOpened &&
|
||||||
|
userContext.features.notebooksTemporarilyDown === false &&
|
||||||
|
userContext.features.phoenix === false
|
||||||
|
) {
|
||||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -213,23 +226,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const connectGitContextMenu: TreeNodeMenuItem[] = [
|
gitHubNotebooksTree.contextMenu = manageGitContextMenu;
|
||||||
{
|
|
||||||
label: "Connect to GitHub",
|
|
||||||
onClick: () =>
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"Connect to GitHub",
|
|
||||||
<GitHubReposPanel
|
|
||||||
explorer={container}
|
|
||||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
|
||||||
junoClientProp={container.notebookManager.junoClient}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
gitHubNotebooksTree.contextMenu = isConnected ? manageGitContextMenu : connectGitContextMenu;
|
|
||||||
gitHubNotebooksTree.isExpanded = true;
|
gitHubNotebooksTree.isExpanded = true;
|
||||||
gitHubNotebooksTree.isAlphaSorted = true;
|
gitHubNotebooksTree.isAlphaSorted = true;
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ import UserDefinedFunction from "./UserDefinedFunction";
|
|||||||
|
|
||||||
export class ResourceTreeAdapter implements ReactAdapter {
|
export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
public static readonly MyNotebooksTitle = "My Notebooks";
|
public static readonly MyNotebooksTitle = "My Notebooks";
|
||||||
public static readonly MyNotebooksScratchTitle = "My Notebooks Scratch";
|
|
||||||
public static readonly GitHubReposTitle = "GitHub repos";
|
public static readonly GitHubReposTitle = "GitHub repos";
|
||||||
|
|
||||||
private static readonly DataTitle = "DATA";
|
private static readonly DataTitle = "DATA";
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { ContainerConnectionInfo } from "../Contracts/DataModels";
|
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
|
||||||
@ -15,7 +13,6 @@ export interface IPhoenixConnectionInfoResult {
|
|||||||
}
|
}
|
||||||
export interface IProvosionData {
|
export interface IProvosionData {
|
||||||
cosmosEndpoint: string;
|
cosmosEndpoint: string;
|
||||||
resourceId: string;
|
|
||||||
dbAccountName: string;
|
dbAccountName: string;
|
||||||
aadToken: string;
|
aadToken: string;
|
||||||
resourceGroup: string;
|
resourceGroup: string;
|
||||||
@ -26,11 +23,7 @@ export class PhoenixClient {
|
|||||||
provisionData: IProvosionData
|
provisionData: IProvosionData
|
||||||
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
||||||
try {
|
try {
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/allocate`, {
|
||||||
status: ConnectionStatusType.Connecting,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: PhoenixClient.getHeaders(),
|
headers: PhoenixClient.getHeaders(),
|
||||||
body: JSON.stringify(provisionData),
|
body: JSON.stringify(provisionData),
|
||||||
@ -38,31 +31,20 @@ export class PhoenixClient {
|
|||||||
let data: IPhoenixConnectionInfoResult;
|
let data: IPhoenixConnectionInfoResult;
|
||||||
if (response.status === HttpStatusCodes.OK) {
|
if (response.status === HttpStatusCodes.OK) {
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
if (data && data.notebookServerUrl) {
|
|
||||||
connectionStatus.status = ConnectionStatusType.Connected;
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connectionStatus.status = ConnectionStatusType.Failed;
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const connectionStatus: ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Failed,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getPhoenixEndpoint(): string {
|
public static getPhoenixEndpoint(): string {
|
||||||
const phoenixEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
const phoenixEndpoint =
|
||||||
|
userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||||
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
|
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
|
||||||
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -73,7 +55,7 @@ export class PhoenixClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getPhoenixContainerPoolingEndPoint(): string {
|
public getPhoenixContainerPoolingEndPoint(): string {
|
||||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/containerpooling`;
|
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer`;
|
||||||
}
|
}
|
||||||
private static getHeaders(): HeadersInit {
|
private static getHeaders(): HeadersInit {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
|
@ -20,6 +20,7 @@ export type Features = {
|
|||||||
readonly enableKoResourceTree: boolean;
|
readonly enableKoResourceTree: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
readonly junoEndpoint?: string;
|
readonly junoEndpoint?: string;
|
||||||
|
readonly phoenixEndpoint?: string;
|
||||||
readonly livyEndpoint?: string;
|
readonly livyEndpoint?: string;
|
||||||
readonly notebookBasePath?: string;
|
readonly notebookBasePath?: string;
|
||||||
readonly notebookServerToken?: string;
|
readonly notebookServerToken?: string;
|
||||||
@ -68,6 +69,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
mongoProxyEndpoint: get("mongoproxyendpoint"),
|
mongoProxyEndpoint: get("mongoproxyendpoint"),
|
||||||
mongoProxyAPIs: get("mongoproxyapis"),
|
mongoProxyAPIs: get("mongoproxyapis"),
|
||||||
junoEndpoint: get("junoendpoint"),
|
junoEndpoint: get("junoendpoint"),
|
||||||
|
phoenixEndpoint: get("phoenixendpoint"),
|
||||||
livyEndpoint: get("livyendpoint"),
|
livyEndpoint: get("livyendpoint"),
|
||||||
notebookBasePath: get("notebookbasepath"),
|
notebookBasePath: get("notebookbasepath"),
|
||||||
notebookServerToken: get("notebookservertoken"),
|
notebookServerToken: get("notebookservertoken"),
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
SortBy,
|
SortBy,
|
||||||
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@ -225,67 +226,89 @@ export function downloadItem(
|
|||||||
const name = data.name;
|
const name = data.name;
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
`Download to ${useNotebook.getState().notebookFolderName}`,
|
`Download to ${useNotebook.getState().notebookFolderName}`,
|
||||||
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
undefined,
|
||||||
"Download",
|
"Download",
|
||||||
async () => {
|
async () => {
|
||||||
const clearInProgressMessage = logConsoleProgress(
|
if (NotebookUtil.isPhoenixEnabled()) {
|
||||||
`Downloading ${name} to ${useNotebook.getState().notebookFolderName}`
|
await container.allocateContainer();
|
||||||
|
}
|
||||||
|
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||||
|
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||||
|
downloadNotebookItem(name, data, junoClient, container, onComplete);
|
||||||
|
} else {
|
||||||
|
useDialog
|
||||||
|
.getState()
|
||||||
|
.showOkModalDialog(
|
||||||
|
"Failed to Connect",
|
||||||
|
"Failed to connect to temporary workspace. Please refresh the page and try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
undefined,
|
||||||
|
container.getDownloadModalConent(name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export async function downloadNotebookItem(
|
||||||
|
fileName: string,
|
||||||
|
data: IGalleryItem,
|
||||||
|
junoClient: JunoClient,
|
||||||
|
container: Explorer,
|
||||||
|
onComplete: (item: IGalleryItem) => void
|
||||||
|
) {
|
||||||
|
const clearInProgressMessage = logConsoleProgress(
|
||||||
|
`Downloading ${fileName} to ${useNotebook.getState().notebookFolderName}`
|
||||||
|
);
|
||||||
|
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
||||||
|
notebookId: data.id,
|
||||||
|
downloadCount: data.downloads,
|
||||||
|
isSample: data.isSample,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await junoClient.getNotebookContent(data.id);
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const notebook = JSON.parse(response.data) as Notebook;
|
||||||
|
removeNotebookViewerLink(notebook, data.newCellId);
|
||||||
|
|
||||||
|
if (!data.isSample) {
|
||||||
|
const metadata = notebook.metadata as { [name: string]: unknown };
|
||||||
|
metadata.untrusted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await container.importAndOpenContent(data.name, JSON.stringify(notebook));
|
||||||
|
logConsoleInfo(`Successfully downloaded ${data.name} to ${useNotebook.getState().notebookFolderName}`);
|
||||||
|
|
||||||
|
const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id);
|
||||||
|
if (increaseDownloadResponse.data) {
|
||||||
|
traceSuccess(
|
||||||
|
Action.NotebooksGalleryDownload,
|
||||||
|
{ notebookId: data.id, downloadCount: increaseDownloadResponse.data.downloads, isSample: data.isSample },
|
||||||
|
startKey
|
||||||
);
|
);
|
||||||
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
onComplete(increaseDownloadResponse.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
traceFailure(
|
||||||
|
Action.NotebooksGalleryDownload,
|
||||||
|
{
|
||||||
notebookId: data.id,
|
notebookId: data.id,
|
||||||
downloadCount: data.downloads,
|
downloadCount: data.downloads,
|
||||||
isSample: data.isSample,
|
isSample: data.isSample,
|
||||||
});
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`);
|
||||||
const response = await junoClient.getNotebookContent(data.id);
|
}
|
||||||
if (!response.data) {
|
|
||||||
throw new Error(`Received HTTP ${response.status} when fetching ${data.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const notebook = JSON.parse(response.data) as Notebook;
|
clearInProgressMessage();
|
||||||
removeNotebookViewerLink(notebook, data.newCellId);
|
|
||||||
|
|
||||||
if (!data.isSample) {
|
|
||||||
const metadata = notebook.metadata as { [name: string]: unknown };
|
|
||||||
metadata.untrusted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await container.importAndOpenContent(data.name, JSON.stringify(notebook));
|
|
||||||
logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`);
|
|
||||||
|
|
||||||
const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id);
|
|
||||||
if (increaseDownloadResponse.data) {
|
|
||||||
traceSuccess(
|
|
||||||
Action.NotebooksGalleryDownload,
|
|
||||||
{ notebookId: data.id, downloadCount: increaseDownloadResponse.data.downloads, isSample: data.isSample },
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
onComplete(increaseDownloadResponse.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
traceFailure(
|
|
||||||
Action.NotebooksGalleryDownload,
|
|
||||||
{
|
|
||||||
notebookId: data.id,
|
|
||||||
downloadCount: data.downloads,
|
|
||||||
isSample: data.isSample,
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
|
|
||||||
handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearInProgressMessage();
|
|
||||||
},
|
|
||||||
"Cancel",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
|
export const removeNotebookViewerLink = (notebook: Notebook, newCellId: string): void => {
|
||||||
if (!newCellId) {
|
if (!newCellId) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user