Compare commits

..

6 Commits

Author SHA1 Message Date
kcheekuri
db8a6f9c5c Update comments-2 2021-12-11 17:42:26 -05:00
kcheekuri
7b332388e1 Format issue 2021-12-11 16:17:00 -05:00
kcheekuri
a9e1c2fba8 Merge branch 'master' into users/kcheekuri/updatePhoenixEndpoints 2021-12-11 16:04:46 -05:00
kcheekuri
004c38c6b8 Address review coments 2021-12-11 14:11:32 -05:00
kcheekuri
a4be933f70 Disable phoenix for vnet and firewall 2021-12-10 10:25:48 -05:00
kcheekuri
288c85d13c Update Api end points and add brs for allowlist 2021-11-16 07:27:00 -05:00
45 changed files with 252 additions and 30130 deletions

View File

@@ -1,4 +1,4 @@
{
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
"isTerminalEnabled" : true
"IS_MPAC" : true
}

View File

@@ -1,4 +1,4 @@
{
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
"isTerminalEnabled" : false
"IS_MPAC" : false
}

View File

@@ -2885,10 +2885,6 @@ a:link {
.settingsSectionPart {
padding-left: 8px;
label {
padding: 0 !important;
font-size: inherit;
}
}
.settingsSectionLabel {
@@ -2899,14 +2895,6 @@ a:link {
.pageOptionsPart {
padding-bottom: @MediumSpace;
}
.legendLabel {
border-bottom: 0px;
width: auto;
font-size: @mediumFontSize;
display: inline !important;
float: left;
}
}
// TODO: Remove these styles once we refactor all buttons to use the command button component

29666
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -96,8 +96,7 @@ export class Flights {
public static readonly AutoscaleTest = "autoscaletest";
public static readonly PartitionKeyTest = "partitionkeytest";
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
public static readonly PhoenixNotebooks = "phoenixnotebooks";
public static readonly PhoenixFeatures = "phoenixfeatures";
public static readonly Phoenix = "phoenix";
public static readonly NotebooksDownBanner = "notebooksdownbanner";
}
@@ -366,7 +365,7 @@ export class Notebook {
public static readonly containerStatusHeartbeatDelayMs = 30000;
public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 300000;
public static readonly autoSaveIntervalMs = 120000;
public static readonly memoryGuageToGB = 1048576;
public static readonly lowMemoryThreshold = 0.8;
public static readonly remainingTimeForAlert = 10;
@@ -379,7 +378,7 @@ export class Notebook {
"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.";
"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 to temporary workspace";
public static newNotebookModalContent1 =

View File

@@ -1,6 +1,5 @@
import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { CosmosHeaders } from "@azure/cosmos/dist-esm";
import { configContext, Platform } from "../ConfigContext";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
@@ -78,21 +77,10 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
}
}
// The Capability is a bitmap, which cosmosdb backend decodes as per the below enum
enum SDKSupportedCapabilities {
None = 0,
PartitionMerge = 1 << 0,
}
let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient {
if (_client) return _client;
let _defaultHeaders: CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supported-capabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey,
@@ -101,7 +89,6 @@ export function client(): Cosmos.CosmosClient {
enableEndpointDiscovery: false,
},
userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders,
};
if (configContext.PROXY_PATH !== undefined) {

View File

@@ -27,7 +27,7 @@ export interface ConfigContext {
GITHUB_CLIENT_ID: string;
GITHUB_TEST_ENV_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
isTerminalEnabled: boolean;
IS_MPAC: boolean;
hostedExplorerURL: string;
armAPIVersion?: string;
allowedJunoOrigins: string[];
@@ -58,9 +58,9 @@ let configContext: Readonly<ConfigContext> = {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
IS_MPAC: false,
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
isTerminalEnabled: false,
allowedJunoOrigins: [
JunoEndpoints.Test,
JunoEndpoints.Test2,

View File

@@ -33,7 +33,6 @@ export enum MessageTypes {
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
CloseTab,
}
export { Versions, ActionContracts, Diagnostics };

View File

@@ -83,6 +83,7 @@ export const createCollectionContextMenuButton = (
items.push({
iconSrc: HostedTerminalIcon,
isDisabled: useNotebook.getState().isShellEnabled && !useNotebook.getState().isPhoenix,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
if (useNotebook.getState().isShellEnabled) {

View File

@@ -55,7 +55,6 @@ describe("NotebookTerminalComponent", () => {
const props: NotebookTerminalComponentProps = {
databaseAccount: testAccount,
notebookServerInfo: testNotebookServerInfo,
tabId: undefined,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
@@ -66,7 +65,6 @@ describe("NotebookTerminalComponent", () => {
const props: NotebookTerminalComponentProps = {
databaseAccount: testMongo32Account,
notebookServerInfo: testMongoNotebookServerInfo,
tabId: undefined,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
@@ -77,7 +75,6 @@ describe("NotebookTerminalComponent", () => {
const props: NotebookTerminalComponentProps = {
databaseAccount: testMongo36Account,
notebookServerInfo: testMongoNotebookServerInfo,
tabId: undefined,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
@@ -88,7 +85,6 @@ describe("NotebookTerminalComponent", () => {
const props: NotebookTerminalComponentProps = {
databaseAccount: testCassandraAccount,
notebookServerInfo: testCassandraNotebookServerInfo,
tabId: undefined,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />);

View File

@@ -12,7 +12,6 @@ import * as StringUtils from "../../../Utils/StringUtils";
export interface NotebookTerminalComponentProps {
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
databaseAccount: DataModels.DatabaseAccount;
tabId: string;
}
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
@@ -56,7 +55,6 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
apiType: userContext.apiType,
authType: userContext.authType,
databaseAccount: userContext.databaseAccount,
tabId: this.props.tabId,
};
postRobot.send(this.terminalWindow, "props", props, {

View File

@@ -439,7 +439,7 @@ export default class Explorer {
);
return;
}
const dialogContent = useNotebook.getState().isPhoenixNotebooks
const dialogContent = useNotebook.getState().isPhoenix
? "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?"
: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?";
@@ -516,7 +516,7 @@ export default class Explorer {
TelemetryProcessor.traceStart(Action.PhoenixResetWorkspace, {
dataExplorerArea: Areas.Notebook,
});
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
useTabs.getState().closeAllNotebookTabs(true);
connectionStatus = {
status: ConnectionStatusType.Connecting,
@@ -530,7 +530,7 @@ export default class Explorer {
if (!connectionInfo?.data?.notebookServerUrl) {
throw new Error(`Reset Workspace: NotebookServerUrl is invalid!`);
}
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
await this.setNotebookInfo(connectionInfo, connectionStatus);
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
}
@@ -545,7 +545,7 @@ export default class Explorer {
error: getErrorMessage(error),
errorStack: getErrorStack(error),
});
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
connectionStatus = {
status: ConnectionStatusType.Failed,
};
@@ -743,7 +743,7 @@ export default class Explorer {
if (!notebookContentItem || !notebookContentItem.path) {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
}
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) {
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenix) {
await this.allocateContainer();
}
@@ -961,17 +961,19 @@ export default class Explorer {
/**
* This creates a new notebook file, then opens the notebook
*/
public async onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
public onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create new notebook, but notebook is not enabled";
handleError(error, "Explorer/onNewNotebookClicked");
throw new Error(error);
}
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
if (isGithubTree) {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
async () => {
await this.allocateContainer();
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
};
} else {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookModalTitle,
@@ -1056,7 +1058,7 @@ export default class Explorer {
}
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
if (useNotebook.getState().isPhoenixFeatures) {
if (useNotebook.getState().isPhoenix) {
await this.allocateContainer();
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
@@ -1096,7 +1098,7 @@ export default class Explorer {
const terminalTabs: TerminalTab[] = useTabs
.getState()
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle().startsWith(title)) as TerminalTab[];
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle() === title) as TerminalTab[];
let index = 1;
if (terminalTabs.length > 0) {
@@ -1197,7 +1199,7 @@ export default class Explorer {
}
public async handleOpenFileAction(path: string): Promise<void> {
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
await this.allocateContainer();
} else if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) {
this._openSetupNotebooksPaneForQuickstart();
@@ -1231,7 +1233,7 @@ export default class Explorer {
}
public openUploadFilePanel(parent?: NotebookContentItem): void {
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookUploadModalTitle,
undefined,
@@ -1261,7 +1263,7 @@ export default class Explorer {
}
public getDownloadModalConent(fileName: string): JSX.Element {
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
return (
<>
<p>{Notebook.galleryNotebookDownloadContent1}</p>
@@ -1284,19 +1286,21 @@ export default class Explorer {
: this.refreshAllDatabases();
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
//Disable phoenix in case of Vnet or Firewall was enabled.
if (!isPublicInternetAccessAllowed()) {
useNotebook.getState().setIsPhoenix(false);
}
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenixNotebooks;
const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenix;
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
useNotebook
.getState()
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed());
useNotebook.getState().setIsShellEnabled(useNotebook.getState().isPhoenix && isPublicInternetAccessAllowed());
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
isNotebookEnabled,
dataExplorerArea: Constants.Areas.Notebook,
});
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
await this.initNotebooks(userContext.databaseAccount);
}
}

View File

@@ -53,7 +53,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) {
if (useNotebook.getState().isPhoenix) {
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
}

View File

@@ -31,13 +31,28 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
});
it("Button should be visible", () => {
it("Account is not serverless - button should be visible", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
expect(enableAzureSynapseLinkBtn).toBeDefined();
});
it("Account is serverless - button should be hidden", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableServerless" }],
},
} as DatabaseAccount,
});
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableAzureSynapseLinkBtn = buttons.find(
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel
);
expect(enableAzureSynapseLinkBtn).toBeUndefined();
});
});
describe("Enable notebook button", () => {

View File

@@ -25,6 +25,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
import { JunoClient } from "../../../Juno/JunoClient";
import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
@@ -77,10 +78,10 @@ export function createStaticCommandBarButtons(
if (container.notebookManager?.gitHubOAuthService) {
notebookButtons.push(createManageGitHubAccountButton(container));
}
if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) {
if (useNotebook.getState().isPhoenix && configContext.IS_MPAC) {
notebookButtons.push(createOpenTerminalButton(container));
}
if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) {
if (selectedNodeState.isConnectedToContainer()) {
notebookButtons.push(createNotebookWorkspaceResetButton(container));
}
if (
@@ -98,21 +99,19 @@ export function createStaticCommandBarButtons(
}
notebookButtons.forEach((btn) => {
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
if (!useNotebook.getState().isPhoenixFeatures) {
if (!useNotebook.getState().isPhoenix) {
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
}
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
if (!useNotebook.getState().isPhoenixFeatures) {
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
} else {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
} else if (!useNotebook.getState().isPhoenixNotebooks) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
buttons.push(btn);
});
} else {
if (!isRunningOnNationalCloud() && useNotebook.getState().isPhoenixNotebooks) {
if (!isRunningOnNationalCloud() && useNotebook.getState().isPhoenix) {
buttons.push(createDivider());
buttons.push(createEnableNotebooksButton(container));
}
@@ -170,7 +169,9 @@ export function createContextCommandBarButtons(
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (useNotebook.getState().isShellEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
if (useNotebook.getState().isPhoenix) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
}
} else {
selectedCollection && selectedCollection.onNewMongoShellClick();
}
@@ -178,6 +179,13 @@ export function createContextCommandBarButtons(
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
tooltipText:
useNotebook.getState().isShellEnabled && !useNotebook.getState().isPhoenix
? Constants.Notebook.mongoShellTemporarilyDownMsg
: undefined,
disabled:
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
(useNotebook.getState().isShellEnabled && !useNotebook.getState().isPhoenix),
};
buttons.push(newMongoShellBtn);
}
@@ -273,6 +281,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
return undefined;
}
if (isServerlessAccount()) {
return undefined;
}
if (userContext?.databaseAccount?.properties?.enableAnalyticalStorage) {
return undefined;
}

View File

@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Link } from "@fluentui/react";
import { CellId, CellType, ImmutableNotebook } from "@nteract/commutable";
// Vendor modules
import {
@@ -15,15 +14,13 @@ import "@nteract/styles/editor-overrides.css";
import "@nteract/styles/global-variables.css";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/lib/codemirror.css";
import { Notebook } from "Common/Constants";
import { useDialog } from "Explorer/Controls/Dialog";
import * as Immutable from "immutable";
import * as React from "react";
import { Provider } from "react-redux";
import "react-table/react-table.css";
import { AnyAction, Store } from "redux";
import { NotebookClientV2 } from "../NotebookClientV2";
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
import { NotebookUtil } from "../NotebookUtil";
import * as NteractUtil from "../NTeractUtil";
import * as CdbActions from "./actions";
import { NotebookComponent } from "./NotebookComponent";
@@ -102,10 +99,6 @@ export class NotebookComponentBootstrapper {
};
}
public getNotebookPath(): string {
return this.getStore().getState().core.entities.contents.byRef.get(this.contentRef)?.filepath;
}
public setContent(name: string, content: unknown): void {
this.getStore().dispatch(
actions.fetchContentFulfilled({
@@ -137,32 +130,11 @@ export class NotebookComponentBootstrapper {
/* Notebook operations. See nteract/packages/connected-components/src/notebook-menu/index.tsx */
public notebookSave(): void {
if (
NotebookUtil.getContentProviderType(this.getNotebookPath()) ===
NotebookContentProviderType.JupyterContentProviderType
) {
useDialog.getState().showOkCancelModalDialog(
Notebook.saveNotebookModalTitle,
undefined,
"Save",
async () => {
this.getStore().dispatch(
actions.save({
contentRef: this.contentRef,
})
);
},
"Cancel",
undefined,
this.getSaveNotebookSubText()
);
} else {
this.getStore().dispatch(
actions.save({
contentRef: this.contentRef,
})
);
}
this.getStore().dispatch(
actions.save({
contentRef: this.contentRef,
})
);
}
public notebookChangeKernel(kernelSpecName: string): void {
@@ -369,19 +341,4 @@ export class NotebookComponentBootstrapper {
protected getStore(): Store<AppState, AnyAction> {
return this.notebookClient.getStore();
}
private getSaveNotebookSubText(): JSX.Element {
return (
<>
<p>{Notebook.saveNotebookModalContent}</p>
<br />
<p>
{Notebook.newNotebookModalContent2}
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
{Notebook.learnMore}
</Link>
</p>
</>
);
}
}

View File

@@ -12,12 +12,11 @@ import {
ServerConfig as JupyterServerConfig,
} from "@nteract/core";
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
import { defineConfigOption } from "@nteract/mythic-configuration";
import { RecordOf } from "immutable";
import { Action, AnyAction } from "redux";
import { AnyAction } from "redux";
import { ofType, StateObservable } from "redux-observable";
import { kernels, sessions } from "rx-jupyter";
import { concat, EMPTY, from, interval, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
import { concat, EMPTY, from, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
import {
catchError,
concatMap,
@@ -42,7 +41,7 @@ import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationCons
import { useDialog } from "../../Controls/Dialog";
import * as FileSystemUtil from "../FileSystemUtil";
import * as cdbActions from "../NotebookComponent/actions";
import { NotebookContentProviderType, NotebookUtil } from "../NotebookUtil";
import { NotebookUtil } from "../NotebookUtil";
import * as CdbActions from "./actions";
import * as TextFile from "./contents/file/text-file";
import { CdbAppState } from "./types";
@@ -949,54 +948,6 @@ const resetCellStatusOnExecuteCanceledEpic = (
);
};
const { selector: autoSaveInterval } = defineConfigOption({
key: "autoSaveInterval",
label: "Auto-save interval",
defaultValue: 120_000,
});
/**
* Override autoSaveCurrentContentEpic to disable auto save for notebooks under temporary workspace.
* @param action$
*/
export function autoSaveCurrentContentEpic(
action$: Observable<Action>,
state$: StateObservable<AppState>
): Observable<actions.Save> {
return state$.pipe(
map((state) => autoSaveInterval(state)),
switchMap((time) => interval(time)),
mergeMap(() => {
const state = state$.value;
return from(
selectors
.contentByRef(state)
.filter(
/*
* Only save contents that are files or notebooks with
* a filepath already set.
*/
(content) => (content.type === "file" || content.type === "notebook") && content.filepath !== ""
)
.keys()
);
}),
filter((contentRef: ContentRef) => {
const model = selectors.model(state$.value, { contentRef });
const content = selectors.content(state$.value, { contentRef });
if (
model &&
model.type === "notebook" &&
NotebookUtil.getContentProviderType(content.filepath) !== NotebookContentProviderType.JupyterContentProviderType
) {
return selectors.notebook.isDirty(model);
}
return false;
}),
map((contentRef: ContentRef) => actions.save({ contentRef }))
);
}
export const allEpics = [
addInitialCodeCellEpic,
focusInitialCodeCellEpic,
@@ -1014,5 +965,4 @@ export const allEpics = [
traceNotebookInfoEpic,
traceNotebookKernelEpic,
resetCellStatusOnExecuteCanceledEpic,
autoSaveCurrentContentEpic,
];

View File

@@ -1,12 +1,12 @@
import { AppState, epics as coreEpics, IContentProvider, reducers } from "@nteract/core";
import { AppState, epics as coreEpics, reducers, IContentProvider } from "@nteract/core";
import { compose, Store, AnyAction, Middleware, Dispatch, MiddlewareAPI } from "redux";
import { Epic } from "redux-observable";
import { allEpics } from "./epics";
import { coreReducer, cdbReducer } from "./reducers";
import { catchError } from "rxjs/operators";
import { Observable } from "rxjs";
import { configuration } from "@nteract/mythic-configuration";
import { makeConfigureStore } from "@nteract/myths";
import { AnyAction, compose, Dispatch, Middleware, MiddlewareAPI, Store } from "redux";
import { Epic } from "redux-observable";
import { Observable } from "rxjs";
import { catchError } from "rxjs/operators";
import { allEpics } from "./epics";
import { cdbReducer, coreReducer } from "./reducers";
import { CdbAppState } from "./types";
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
@@ -81,6 +81,7 @@ export const getCoreEpics = (autoStartKernelOnNotebookOpen: boolean): Epic[] =>
// This list needs to be consistent and in sync with core.allEpics until we figure
// out how to safely filter out the ones we are overriding here.
const filteredCoreEpics = [
coreEpics.autoSaveCurrentContentEpic,
coreEpics.executeCellEpic,
coreEpics.executeFocusedCellEpic,
coreEpics.executeCellAfterKernelLaunchEpic,

View File

@@ -121,10 +121,13 @@ export class NotebookContainerClient {
}
private shouldExecuteMemoryCall(): boolean {
return (
if (
useNotebook.getState().containerStatus?.status === Constants.ContainerStatusType.Active &&
useNotebook.getState().connectionInfo?.status === ConnectionStatusType.Connected
);
) {
return true;
}
return false;
}
public async resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
@@ -149,7 +152,7 @@ export class NotebookContainerClient {
}
try {
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
const provisionData: IProvisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
};

View File

@@ -5,17 +5,11 @@ import Html2Canvas from "html2canvas";
import path from "path";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as StringUtils from "../../Utils/StringUtils";
import * as InMemoryContentProviderUtils from "../Notebook/NotebookComponent/ContentProviders/InMemoryContentProviderUtils";
import { SnapshotFragment } from "./NotebookComponent/types";
import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentItem";
// Must match rx-jupyter' FileType
export type FileType = "directory" | "file" | "notebook";
export enum NotebookContentProviderType {
GitHubContentProviderType,
InMemoryContentProviderType,
JupyterContentProviderType,
}
// Utilities for notebooks
export class NotebookUtil {
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
@@ -132,18 +126,6 @@ export class NotebookUtil {
return relativePath.split("/").pop();
}
public static getContentProviderType(path: string): NotebookContentProviderType {
if (InMemoryContentProviderUtils.fromContentUri(path)) {
return NotebookContentProviderType.InMemoryContentProviderType;
}
if (GitHubUtils.fromContentUri(path)) {
return NotebookContentProviderType.GitHubContentProviderType;
}
return NotebookContentProviderType.JupyterContentProviderType;
}
public static replaceName(path: string, newName: string): string {
const contentInfo = GitHubUtils.fromContentUri(path);
if (contentInfo) {

View File

@@ -1,4 +1,3 @@
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { cloneDeep } from "lodash";
import { PhoenixClient } from "Phoenix/PhoenixClient";
import create, { UseStore } from "zustand";
@@ -38,8 +37,7 @@ interface NotebookState {
isAllocating: boolean;
isRefreshed: boolean;
containerStatus: ContainerInfo;
isPhoenixNotebooks: boolean;
isPhoenixFeatures: boolean;
isPhoenix: boolean;
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
@@ -62,8 +60,7 @@ interface NotebookState {
setIsRefreshed: (isAllocating: boolean) => void;
setContainerStatus: (containerStatus: ContainerInfo) => void;
getPhoenixStatus: () => Promise<void>;
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => void;
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => void;
setIsPhoenix: (isPhoenix: boolean) => void;
}
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
@@ -98,8 +95,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
durationLeftInMinutes: undefined,
notebookServerInfo: undefined,
},
isPhoenixNotebooks: undefined,
isPhoenixFeatures: undefined,
isPhoenix: undefined,
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
@@ -205,7 +201,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const notebookFolderName = get().isPhoenixNotebooks ? "Temporary Notebooks" : "My Notebooks";
const notebookFolderName = get().isPhoenix === true ? "Temporary Notebooks" : "My Notebooks";
set({ notebookFolderName });
const myNotebooksContentRoot = {
name: get().notebookFolderName,
@@ -302,20 +298,11 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
getPhoenixStatus: async () => {
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
let isPhoenix = false;
if (userContext.features.phoenixNotebooks || userContext.features.phoenixFeatures) {
const phoenixClient = new PhoenixClient();
isPhoenix = isPublicInternetAccessAllowed() && (await phoenixClient.isDbAcountWhitelisted());
}
const isPhoenixNotebooks = userContext.features.phoenixNotebooks && isPhoenix;
const isPhoenixFeatures = userContext.features.phoenixFeatures && isPhoenix;
set({ isPhoenixNotebooks: isPhoenixNotebooks });
set({ isPhoenixFeatures: isPhoenixFeatures });
if (get().isPhoenix === undefined) {
const phoenixClient = new PhoenixClient();
const isPhoenix = userContext.features.phoenix === true && (await phoenixClient.IsDbAcountWhitelisted());
set({ isPhoenix });
}
},
setIsPhoenixNotebooks: (isPhoenixNotebooks: boolean) => set({ isPhoenixNotebooks: isPhoenixNotebooks }),
setIsPhoenixFeatures: (isPhoenixFeatures: boolean) => set({ isPhoenixFeatures: isPhoenixFeatures }),
setIsPhoenix: (isPhoenix: boolean) => set({ isPhoenix }),
}));

View File

@@ -25,7 +25,6 @@ export const OpenFullScreen: React.FunctionComponent = () => {
<TextField label="Read and Write" readOnly defaultValue={readWriteUrl} />
<Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
ariaLabel={isReadWriteUrlCopy ? "Copied url" : "Copy"}
onClick={() => {
copyToClipboard(readWriteUrl);
setIsReadWriteUrlCopy(true);
@@ -44,7 +43,6 @@ export const OpenFullScreen: React.FunctionComponent = () => {
<TextField label="Read Only" readOnly defaultValue={readUrl} />
<Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
ariaLabel={isReadUrlCopy ? "Copied url" : "Copy"}
onClick={() => {
setIsReadUrlCopy(true);
copyToClipboard(readUrl);

View File

@@ -279,7 +279,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{`${getCollectionName()} id`}
{`${getCollectionName()} ${userContext.apiType === "Mongo" ? "name" : "id"}`}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
@@ -667,7 +667,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{userContext.apiType === "SQL" && (
<Checkbox
label="My partition key is larger than 101 bytes"
label="My partition key is larger than 100 bytes"
checked={this.state.useHashV2}
styles={{
text: { fontSize: 12 },
@@ -887,6 +887,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false;
}
if (isServerlessAccount()) {
return false;
}
switch (userContext.apiType) {
case "SQL":
case "Mongo":

View File

@@ -334,7 +334,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false}
isSharded
isSharded={false}
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}

View File

@@ -75,7 +75,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
selectedLocation.owner,
selectedLocation.repo
)} - ${selectedLocation.branch}`;
} else if (selectedLocation.type === "MyNotebooks" && useNotebook.getState().isPhoenixNotebooks) {
} else if (selectedLocation.type === "MyNotebooks" && useNotebook.getState().isPhoenix) {
destination = useNotebook.getState().notebookFolderName;
}

View File

@@ -38,7 +38,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const onSubmit = async (): Promise<void> => {
const collection = useSelectedNode.getState().findSelectedCollection();
if (!collection || inputCollectionName !== collection.id()) {
const errorMessage = "Input " + collectionName + " id does not match the selected " + collectionName;
const errorMessage = "Input " + collectionName + " name does not match the selected " + collectionName;
setFormError(errorMessage);
NotificationConsoleUtils.logConsoleError(
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`

View File

@@ -1,4 +1,4 @@
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton } from "@fluentui/react";
import { Checkbox, ChoiceGroup, IChoiceGroupOption, SpinButton, Stack, Text } from "@fluentui/react";
import * as Constants from "Common/Constants";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { configContext } from "ConfigContext";
@@ -115,15 +115,9 @@ export const SettingsPane: FunctionComponent = () => {
};
const choiceButtonStyles = {
root: {
clear: "both",
},
flexContainer: [
{
selectors: {
".ms-ChoiceFieldGroup root-133": {
clear: "both",
},
".ms-ChoiceField-wrapper label": {
fontSize: 12,
paddingTop: 0,
@@ -141,22 +135,22 @@ export const SettingsPane: FunctionComponent = () => {
{shouldShowQueryPageOptions && (
<div className="settingsSection">
<div className="settingsSectionPart">
<fieldset>
<legend id="pageOptions" className="settingsSectionLabel legendLabel">
Page Options
</legend>
<Stack horizontal>
<Text id="pageOptions" className="settingsSectionLabel" variant="small">
Page options
</Text>
<InfoTooltip>
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
query results per page.
</InfoTooltip>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
/>
</fieldset>
</Stack>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
/>
</div>
<div className="tabs settingsSectionPart">
{isCustomPageOptionSelected() && (
@@ -188,11 +182,14 @@ export const SettingsPane: FunctionComponent = () => {
{shouldShowCrossPartitionOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<label className="settingsSectionLabel">Enable cross-partition query</label>
<InfoTooltip>
Send more than one request while executing a query. More than one request is necessary if the query is
not scoped to single partition key value.
</InfoTooltip>
<div className="settingsSectionLabel">
Enable cross-partition query
<InfoTooltip>
Send more than one request while executing a query. More than one request is necessary if the query is
not scoped to single partition key value.
</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
@@ -208,14 +205,14 @@ export const SettingsPane: FunctionComponent = () => {
{shouldShowParallelismOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<label className="settingsSectionLabel" htmlFor="input65">
<div className="settingsSectionLabel">
Max degree of parallelism
</label>
<InfoTooltip>
Gets or sets the number of concurrent operations run client side during parallel query execution. A
positive property value limits the number of concurrent operations to the set value. If it is set to
less than 0, the system automatically decides the number of concurrent operations to run.
</InfoTooltip>
<InfoTooltip>
Gets or sets the number of concurrent operations run client side during parallel query execution. A
positive property value limits the number of concurrent operations to the set value. If it is set to
less than 0, the system automatically decides the number of concurrent operations to run.
</InfoTooltip>
</div>
<SpinButton
min={-1}

View File

@@ -16,57 +16,54 @@ exports[`Settings Pane should render Default properly 1`] = `
<div
className="settingsSectionPart"
>
<fieldset>
<legend
className="settingsSectionLabel legendLabel"
<Stack
horizontal={true}
>
<Text
className="settingsSectionLabel"
id="pageOptions"
variant="small"
>
Page Options
</legend>
Page options
</Text>
<InfoTooltip>
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page.
</InfoTooltip>
<StyledChoiceGroup
ariaLabelledBy="pageOptions"
onChange={[Function]}
options={
Array [
Object {
"key": "custom",
"text": "Custom",
},
Object {
"key": "unlimited",
"text": "Unlimited",
},
]
}
selectedKey="custom"
styles={
</Stack>
<StyledChoiceGroup
ariaLabelledBy="pageOptions"
onChange={[Function]}
options={
Array [
Object {
"flexContainer": Array [
Object {
"selectors": Object {
".ms-ChoiceField": Object {
"marginTop": 0,
},
".ms-ChoiceField-wrapper label": Object {
"fontSize": 12,
"paddingTop": 0,
},
".ms-ChoiceFieldGroup root-133": Object {
"clear": "both",
},
"key": "custom",
"text": "Custom",
},
Object {
"key": "unlimited",
"text": "Unlimited",
},
]
}
selectedKey="custom"
styles={
Object {
"flexContainer": Array [
Object {
"selectors": Object {
".ms-ChoiceField": Object {
"marginTop": 0,
},
".ms-ChoiceField-wrapper label": Object {
"fontSize": 12,
"paddingTop": 0,
},
},
],
"root": Object {
"clear": "both",
},
}
],
}
/>
</fieldset>
}
/>
</div>
<div
className="tabs settingsSectionPart"
@@ -103,14 +100,14 @@ exports[`Settings Pane should render Default properly 1`] = `
<div
className="settingsSectionPart"
>
<label
<div
className="settingsSectionLabel"
>
Enable cross-partition query
</label>
<InfoTooltip>
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
</InfoTooltip>
<InfoTooltip>
Send more than one request while executing a query. More than one request is necessary if the query is not scoped to single partition key value.
</InfoTooltip>
</div>
<StyledCheckboxBase
ariaLabel="Enable cross partition query"
checked={true}
@@ -132,15 +129,14 @@ exports[`Settings Pane should render Default properly 1`] = `
<div
className="settingsSectionPart"
>
<label
<div
className="settingsSectionLabel"
htmlFor="input65"
>
Max degree of parallelism
</label>
<InfoTooltip>
Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
</InfoTooltip>
<InfoTooltip>
Gets or sets the number of concurrent operations run client side during parallel query execution. A positive property value limits the number of concurrent operations to the set value. If it is set to less than 0, the system automatically decides the number of concurrent operations to run.
</InfoTooltip>
</div>
<StyledSpinButton
ariaLabel="Max degree of parallelism"
className="textfontclr"

View File

@@ -24,7 +24,6 @@ const {
Ascii,
Bigint,
Blob,
Date: DateType,
Decimal,
Float,
Int,
@@ -42,7 +41,6 @@ export const cassandraOptions = [
{ key: Bigint, text: Bigint },
{ key: Blob, text: Blob },
{ key: Boolean, text: Boolean },
{ key: DateType, text: DateType },
{ key: Decimal, text: Decimal },
{ key: Double, text: Double },
{ key: Float, text: Float },

View File

@@ -221,7 +221,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
});
}
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isNotebookEnabled && useNotebook.getState().isPhoenix) {
heroes.push({
iconSrc: NewNotebookIcon,
title: "New Notebook",

View File

@@ -14,7 +14,6 @@ export const CassandraType = {
Bigint: "Bigint",
Blob: "Blob",
Boolean: "Boolean",
Date: "Date",
Decimal: "Decimal",
Double: "Double",
Float: "Float",

View File

@@ -536,8 +536,7 @@ export class CassandraAPIDataClient extends TableDataClient {
dataType === TableConstants.CassandraType.Inet ||
dataType === TableConstants.CassandraType.Ascii ||
dataType === TableConstants.CassandraType.Varchar ||
dataType === TableConstants.CassandraType.Timestamp ||
dataType === TableConstants.CassandraType.Date
dataType === TableConstants.CassandraType.Timestamp
);
}

View File

@@ -364,11 +364,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
}
public onTabClick(): void {
if (!this.isCloseClicked) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
} else {
this.isCloseClicked = false;
}
setTimeout(() => {
if (!this.isCloseClicked) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
} else {
this.isCloseClicked = false;
}
}, 0);
}
public onExecuteQueryClick = async (): Promise<void> => {
@@ -873,11 +875,9 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
componentDidMount(): void {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
render(): JSX.Element {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
return (
<Fragment>
<div className="tab-pane" id={this.props.tabId} role="tabpanel">

View File

@@ -25,8 +25,7 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
public parameters: ko.Computed<boolean>;
constructor(
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
private getDatabaseAccount: () => DataModels.DatabaseAccount,
private getTabId: () => string
private getDatabaseAccount: () => DataModels.DatabaseAccount
) {}
public renderComponent(): JSX.Element {
@@ -34,7 +33,6 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
<NotebookTerminalComponent
notebookServerInfo={this.getNotebookServerInfo()}
databaseAccount={this.getDatabaseAccount()}
tabId={this.getTabId()}
/>
) : (
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
@@ -52,8 +50,7 @@ export default class TerminalTab extends TabsBase {
this.container = options.container;
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
() => this.getNotebookServerInfo(options),
() => userContext?.databaseAccount,
() => this.tabId
() => userContext?.databaseAccount
);
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
if (

View File

@@ -529,7 +529,7 @@ export default class Collection implements ViewModels.Collection {
};
public onSchemaAnalyzerClick = async () => {
if (useNotebook.getState().isPhoenixFeatures) {
if (useNotebook.getState().isPhoenix) {
await this.container.allocateContainer();
}
useSelectedNode.getState().setSelectedNode(this);

View File

@@ -121,7 +121,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
children: [],
};
if (!useNotebook.getState().isPhoenixNotebooks) {
if (!useNotebook.getState().isPhoenix) {
notebooksTree.children.push(buildNotebooksTemporarilyDownTree());
} else {
if (galleryContentRoot) {
@@ -130,7 +130,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
if (
myNotebooksContentRoot &&
useNotebook.getState().isPhoenixNotebooks &&
useNotebook.getState().isPhoenix &&
useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected
) {
notebooksTree.children.push(buildMyNotebooksTree());
@@ -380,11 +380,6 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
},
];
//disallow renaming of temporary notebook workspace
if (item?.path === useNotebook.getState().notebookBasePath) {
items = items.filter((item) => item.label !== "Rename");
}
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
if (GitHubUtils.fromContentUri(item.path)) {
items = items.filter(
@@ -516,7 +511,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
isNotebookEnabled &&
userContext.apiType === "Mongo" &&
isPublicInternetAccessAllowed() &&
useNotebook.getState().isPhoenixFeatures
useNotebook.getState().isPhoenix
) {
children.push({
label: "Schema (Preview)",

View File

@@ -808,11 +808,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
},
];
//disallow renaming of temporary notebook workspace
if (item?.path === useNotebook.getState().notebookBasePath) {
items = items.filter((item) => item.label !== "Rename");
}
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
if (GitHubUtils.fromContentUri(item.path)) {
items = items.filter(

View File

@@ -1,13 +1,5 @@
import promiseRetry, { AbortError } from "p-retry";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import {
Areas,
ConnectionStatusType,
ContainerStatusType,
HttpHeaders,
HttpStatusCodes,
Notebook,
} from "../Common/Constants";
import { ConnectionStatusType, ContainerStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../Common/Constants";
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
import * as Logger from "../Common/Logger";
import { configContext } from "../ConfigContext";
@@ -17,10 +9,9 @@ import {
IContainerData,
IPhoenixConnectionInfoResult,
IProvisionData,
IResponse,
IResponse
} from "../Contracts/DataModels";
import { useNotebook } from "../Explorer/Notebook/useNotebook";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
@@ -45,11 +36,14 @@ export class PhoenixClient {
operation: string
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
try {
const response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
method: operation === "allocate" ? "POST" : "PATCH",
headers: PhoenixClient.getHeaders(),
body: JSON.stringify(provisionData),
});
const response = await fetch(
`${this.getPhoenixControlPlaneEndpoint()}/containerconnections`,
{
method: operation === "allocate" ? "POST" : "PATCH",
headers: PhoenixClient.getHeaders(),
body: JSON.stringify(provisionData),
}
);
let data: IPhoenixConnectionInfoResult;
if (response.status === HttpStatusCodes.OK) {
@@ -82,7 +76,7 @@ export class PhoenixClient {
try {
const runContainerStatusAsync = async () => {
const response = await window.fetch(
`${this.getPhoenixControlPlanePathPrefix()}/${containerData.forwardingId}`,
`${this.getPhoenixControlPlaneEndpoint()}/${containerData.forwardingId}`,
{
method: "GET",
headers: PhoenixClient.getHeaders(),
@@ -96,15 +90,9 @@ export class PhoenixClient {
status: ContainerStatusType.Active,
};
} else if (response.status === HttpStatusCodes.NotFound) {
const error = "Disconnected from compute workspace";
Logger.logError(error, "");
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Reconnect,
};
TelemetryProcessor.traceMark(Action.PhoenixHeartBeat, {
dataExplorerArea: Areas.Notebook,
message: getErrorMessage(error),
});
useNotebook.getState().resetContainerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
throw new AbortError(response.statusText);
@@ -113,12 +101,9 @@ export class PhoenixClient {
};
return await promiseRetry(runContainerStatusAsync, this.retryOptions);
} catch (error) {
TelemetryProcessor.traceFailure(Action.PhoenixHeartBeat, {
dataExplorerArea: Areas.Notebook,
});
Logger.logError(getErrorMessage(error), "");
Logger.logError(getErrorMessage(error), "PhoenixClient/getContainerStatus");
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Failed,
status: ConnectionStatusType.Reconnect,
};
useNotebook.getState().resetContainerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
@@ -138,9 +123,9 @@ export class PhoenixClient {
}
}
public async isDbAcountWhitelisted(): Promise<boolean> {
public async IsDbAcountWhitelisted() {
try {
const response = await window.fetch(`${this.getPhoenixControlPlanePathPrefix()}`, {
const response = await window.fetch(`${this.getPhoenixControlPlaneEndpoint()}`, {
method: "GET",
headers: PhoenixClient.getHeaders(),
});
@@ -163,10 +148,8 @@ export class PhoenixClient {
return phoenixEndpoint;
}
public getPhoenixControlPlanePathPrefix(): string {
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts${
userContext.databaseAccount.id
}`;
public getPhoenixControlPlaneEndpoint(): string {
return `${PhoenixClient.getPhoenixEndpoint()}/api/controlplane/toolscontainer/cosmosaccounts${userContext.databaseAccount.id}`;
}
private static getHeaders(): HeadersInit {

View File

@@ -11,8 +11,7 @@ export type Features = {
autoscaleDefault: boolean;
partitionKeyDefault: boolean;
partitionKeyDefault2: boolean;
phoenixNotebooks: boolean;
phoenixFeatures: boolean;
phoenix: boolean;
notebooksDownBanner: boolean;
readonly enableSDKoperations: boolean;
readonly enableSpark: boolean;
@@ -84,8 +83,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
autoscaleDefault: "true" === get("autoscaledefault"),
partitionKeyDefault: "true" === get("partitionkeytest"),
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
phoenixNotebooks: "true" === get("phoenixnotebooks"),
phoenixFeatures: "true" === get("phoenixfeatures"),
phoenix: "true" === get("phoenix"),
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
};

View File

@@ -82,7 +82,6 @@ export enum Action {
NotebooksMoveCellUpFromMenu,
NotebooksMoveCellDownFromMenu,
PhoenixConnection,
PhoenixHeartBeat,
PhoenixResetWorkspace,
DeleteCellFromMenu,
OpenTerminal,

View File

@@ -2,61 +2,15 @@
* JupyterLab applications based on jupyterLab components
*/
import { ServerConnection, TerminalManager } from "@jupyterlab/services";
import { IMessage } from "@jupyterlab/services/lib/terminal/terminal";
import { Terminal } from "@jupyterlab/terminal";
import { Panel, Widget } from "@phosphor/widgets";
import { userContext } from "UserContext";
export class JupyterLabAppFactory {
private isShellStarted: boolean | undefined;
private checkShellStarted: ((content: string | undefined) => void) | undefined;
private onShellExited: () => void;
private isShellExited(content: string | undefined) {
return content?.includes("cosmosuser@");
}
private isMongoShellStarted(content: string | undefined) {
this.isShellStarted = content?.includes("MongoDB shell version");
}
private isCassandraShellStarted(content: string | undefined) {
this.isShellStarted = content?.includes("Connected to") && content?.includes("cqlsh");
}
constructor(closeTab: () => void) {
this.onShellExited = closeTab;
this.isShellStarted = false;
this.checkShellStarted = undefined;
switch (userContext.apiType) {
case "Mongo":
this.checkShellStarted = this.isMongoShellStarted;
break;
case "Cassandra":
this.checkShellStarted = this.isCassandraShellStarted;
break;
}
}
public async createTerminalApp(serverSettings: ServerConnection.ISettings) {
public static async createTerminalApp(serverSettings: ServerConnection.ISettings) {
const manager = new TerminalManager({
serverSettings: serverSettings,
});
const session = await manager.startNew();
session.messageReceived.connect(async (_, message: IMessage) => {
const content = message.content && message.content[0]?.toString();
if (this.checkShellStarted && message.type == "stdout") {
//Close the terminal tab once the shell closed messages are received
if (!this.isShellStarted) {
this.checkShellStarted(content);
} else if (this.isShellExited(content)) {
this.onShellExited();
}
}
}, this);
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
if (!term) {

View File

@@ -10,5 +10,4 @@ export interface TerminalProps {
authType: AuthType;
apiType: ApiType;
subscriptionId: string;
tabId: string;
}

View File

@@ -1,6 +1,5 @@
import { ServerConnection } from "@jupyterlab/services";
import "@jupyterlab/terminal/style/index.css";
import { MessageTypes } from "Contracts/ExplorerContracts";
import postRobot from "post-robot";
import { HttpHeaders } from "../Common/Constants";
import { Action } from "../Shared/Telemetry/TelemetryConstants";
@@ -55,20 +54,13 @@ const initTerminal = async (props: TerminalProps) => {
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
try {
await new JupyterLabAppFactory(() => closeTab(props.tabId)).createTerminalApp(serverSettings);
await JupyterLabAppFactory.createTerminalApp(serverSettings);
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
} catch (error) {
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
}
};
const closeTab = (tabId: string): void => {
window.parent.postMessage(
{ type: MessageTypes.CloseTab, data: { tabId: tabId }, signature: "pcIframe" },
window.document.referrer
);
};
const main = async (): Promise<void> => {
postRobot.on(
"props",

View File

@@ -228,7 +228,7 @@ export function downloadItem(
undefined,
"Download",
async () => {
if (useNotebook.getState().isPhoenixNotebooks) {
if (useNotebook.getState().isPhoenix) {
await container.allocateContainer();
}
const notebookServerInfo = useNotebook.getState().notebookServerInfo;

View File

@@ -1,4 +1,3 @@
import { useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react";
import { applyExplorerBindings } from "../applyExplorerBindings";
import { AuthType } from "../AuthType";
@@ -70,38 +69,16 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
async function configureHosted(): Promise<Explorer> {
const win = (window as unknown) as HostedExplorerChildFrame;
let explorer: Explorer;
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
explorer = configureHostedWithEncryptedToken(win.hostedConfig);
return configureHostedWithEncryptedToken(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
explorer = configureHostedWithResourceToken(win.hostedConfig);
return configureHostedWithResourceToken(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
explorer = configureHostedWithConnectionString(win.hostedConfig);
return configureHostedWithConnectionString(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.AAD) {
explorer = await configureHostedWithAAD(win.hostedConfig);
} else {
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
return configureHostedWithAAD(win.hostedConfig);
}
window.addEventListener(
"message",
(event) => {
if (isInvalidParentFrameOrigin(event)) {
return;
}
if (!shouldProcessMessage(event)) {
return;
}
if (event.data?.type === MessageTypes.CloseTab) {
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
}
},
false
);
return explorer;
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
}
async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
@@ -284,8 +261,6 @@ async function configurePortal(): Promise<Explorer> {
}
} else if (shouldForwardMessage(message, event.origin)) {
sendMessage(message);
} else if (event.data?.type === MessageTypes.CloseTab) {
useTabs.getState().closeTabsByComparator((tab) => tab.tabId === event.data?.data?.tabId);
}
},
false
@@ -364,11 +339,8 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) {
userContext.features.partitionKeyDefault2 = true;
}
if (inputs.flights.indexOf(Flights.PhoenixNotebooks) !== -1) {
userContext.features.phoenixNotebooks = true;
}
if (inputs.flights.indexOf(Flights.PhoenixFeatures) !== -1) {
userContext.features.phoenixFeatures = true;
if (inputs.flights.indexOf(Flights.Phoenix) !== -1) {
userContext.features.phoenix = true;
}
if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) {
userContext.features.notebooksDownBanner = true;