New MongoQueryTab and component running nteract

This commit is contained in:
Laurent Nguyen
2020-12-02 14:22:02 +01:00
parent 84b6075ee8
commit 0c80c45e22
10 changed files with 306 additions and 46 deletions

View File

@@ -26,7 +26,7 @@ ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponent
ko.components.register("tabs-manager", TabsManagerKOComponent());
// Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
ko.components.register("documents-tab", new TabComponents.MongoDocumentsTabV2());
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
ko.components.register("trigger-tab", new TabComponents.TriggerTab());

View File

@@ -2379,13 +2379,11 @@ export default class Explorer {
this.tabsManager.activateTab(notebookTab);
} else {
const options: NotebookTabOptions = {
account: userContext.databaseAccount,
tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null,
title: notebookContentItem.name,
tabPath: notebookContentItem.path,
collection: null,
masterKey: userContext.masterKey || "",
hashLocation: "notebooks",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),

View File

@@ -0,0 +1,143 @@
import * as React from "react";
import { Dispatch } from "redux";
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
import { PrimaryButton } from "office-ui-fabric-react";
import { ChoiceGroup, IChoiceGroupOption } from "office-ui-fabric-react/lib/ChoiceGroup";
import Input from "@nteract/stateful-components/lib/inputs/input";
import Editor from "@nteract/stateful-components/lib/inputs/editor";
import { Source } from "@nteract/presentational-components";
import Outputs from "@nteract/stateful-components/lib/outputs";
import { KernelOutputError, StreamText } from "@nteract/outputs";
import TransformMedia from "@nteract/stateful-components/lib/outputs/transform-media";
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
import { actions, selectors, AppState, ContentRef, KernelRef } from "@nteract/core";
import { connect } from "react-redux";
interface MongoQueryComponentPureProps {
contentRef: ContentRef;
kernelRef: KernelRef;
}
interface MongoQueryComponentDispatchProps {
runCell: (contentRef: ContentRef, cellId: string) => void;
}
type OutputType = "rich" | "json";
interface MongoQueryComponentState {
outputType: OutputType;
}
const options: IChoiceGroupOption[] = [
{ key: "rich", text: "Rich Output" },
{ key: "json", text: "Json Output" }
];
type MongoQueryComponentProps = MongoQueryComponentPureProps & StateProps & MongoQueryComponentDispatchProps;
export class MongoQueryComponent extends React.Component<MongoQueryComponentProps, MongoQueryComponentState> {
constructor(props: MongoQueryComponentProps) {
super(props);
this.state = {
outputType: "rich"
};
}
private onExecute = () => {
this.props.runCell(this.props.contentRef, this.props.firstCellId);
};
private onOutputTypeChange = (e: React.FormEvent<HTMLElement | HTMLInputElement>): void => {
const outputType = e.target.value as OutputType;
this.setState({ outputType });
};
render(): JSX.Element {
const editor = {
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />
};
const { firstCellId: id, contentRef } = this.props;
if (!id) {
return <>NO FIRST CELL</>;
}
return (
<div style={{ marginLeft: 10 }}>
<div>
<Input id={id} contentRef={contentRef}>
<Source className="nteract-cell-source">
<Editor id={id} contentRef={contentRef}>
{editor}
</Editor>
</Source>
</Input>
<PrimaryButton text="Primary" onClick={this.onExecute} disabled={!this.props.firstCellId} />
<ChoiceGroup
selectedKey={this.state.outputType}
options={options}
onChange={this.onOutputTypeChange}
label="Output Type"
/>
<hr />
<Outputs id={id} contentRef={contentRef}>
<TransformMedia output_type={"display_data"} id={id} contentRef={contentRef} />
<TransformMedia output_type={"execute_result"} id={id} contentRef={contentRef} />
<KernelOutputError />
<StreamText />
</Outputs>
</div>
</div>
);
}
}
interface StateProps {
firstCellId: string;
}
interface InitialProps {
contentRef: string;
}
// Redux
const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => {
const { contentRef } = initialProps;
console.log("makeMapStateToProps");
const mapStateToProps = (state: AppState) => {
let firstCellId;
const content = selectors.content(state, { contentRef });
console.log("Looking for first cell", content?.type, state);
if (content?.type === "notebook") {
const cellOrder = selectors.notebook.cellOrder(content.model);
if (cellOrder.size > 0) {
firstCellId = cellOrder.first() as string;
}
}
return {
firstCellId
};
};
return mapStateToProps;
};
const makeMapDispatchToProps = (/* initialDispatch: Dispatch, initialProps: MongoQueryComponentProps */) => {
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
runCell: (contentRef: ContentRef, cellId: string) => {
return dispatch(
actions.executeCell({
contentRef,
id: cellId
})
);
}
};
};
return mapDispatchToProps;
};
export default connect(makeMapStateToProps, makeMapDispatchToProps)(MongoQueryComponent);

View File

@@ -0,0 +1,43 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
NotebookComponentBootstrapper,
NotebookComponentBootstrapperOptions
} from "../NotebookComponent/NotebookComponentBootstrapper";
import MongoQueryComponent from "../MongoQueryComponent/MongoQueryComponent";
import { actions, createContentRef, createKernelRef, KernelRef } from "@nteract/core";
import { Provider } from "react-redux";
export class MongoQueryComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
public parameters: unknown;
private kernelRef: KernelRef;
constructor(options: NotebookComponentBootstrapperOptions) {
super(options);
if (!this.contentRef) {
this.contentRef = createContentRef();
this.kernelRef = createKernelRef();
// Request fetching notebook content
this.getStore().dispatch(
actions.fetchContent({
filepath: "mongo.ipynb",
params: {},
kernelRef: this.kernelRef,
contentRef: this.contentRef
})
);
}
}
public renderComponent(): JSX.Element {
console.log("Rendering from adapter");
return (
<Provider store={this.getStore()}>
<MongoQueryComponent contentRef={this.contentRef} kernelRef={this.kernelRef} />;
</Provider>
);
}
}

View File

@@ -0,0 +1 @@
<div data-bind="react:mongoQueryComponentAdapter" style="height: 100%"></div>

View File

@@ -0,0 +1,45 @@
import * as Q from "q";
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
import { MongoQueryComponentAdapter } from "../Notebook/MongoQueryComponent/MongoQueryComponentAdapter";
export default class MongoDocumentsTabV2 extends NotebookTabBase {
private mongoQueryComponentAdapter: MongoQueryComponentAdapter;
constructor(options: NotebookTabBaseOptions) {
super(options);
this.mongoQueryComponentAdapter = new MongoQueryComponentAdapter({
contentRef: undefined,
notebookClient: NotebookTabBase.clientManager
});
}
public onCloseTabButtonClick(): Q.Promise<void> {
super.onCloseTabButtonClick();
// const cleanup = () => {
// this.notebookComponentAdapter.notebookShutdown();
// this.isActive(false);
// super.onCloseTabButtonClick();
// };
// if (this.notebookComponentAdapter.isContentDirty()) {
// this.container.showOkCancelModalDialog(
// "Close without saving?",
// `File has unsaved changes, close without saving?`,
// "Close",
// cleanup,
// "Cancel",
// undefined
// );
// return Q.resolve(null);
// } else {
// cleanup();
// return Q.resolve(null);
// }
return undefined;
}
protected buildCommandBarOptions(): void {
this.updateNavbarWithTabsButtons();
}
}

View File

@@ -0,0 +1,50 @@
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import { NotebookClientV2 } from "../Notebook/NotebookClientV2";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import { ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../Common/Constants";
export interface NotebookTabBaseOptions extends ViewModels.TabOptions {
container: Explorer;
}
/**
* Every notebook-based tab inherits from this class. It holds the static reference to a notebook client (singleton)
*/
export default class NotebookTabBase extends TabsBase {
protected static clientManager: NotebookClientV2;
protected container: Explorer;
constructor(options: NotebookTabBaseOptions) {
super(options);
this.container = options.container;
if (!NotebookTabBase.clientManager) {
NotebookTabBase.clientManager = new NotebookClientV2({
connectionInfo: this.container.notebookServerInfo(),
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
contentProvider: this.container.notebookManager?.notebookContentProvider
});
}
}
/**
* Override base behavior
*/
protected getContainer(): Explorer {
return this.container;
}
protected traceTelemetry(actionType: number): void {
TelemetryProcessor.trace(actionType, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook
});
}
}

View File

@@ -2,9 +2,7 @@ import * as _ from "underscore";
import * as Q from "q";
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as DataModels from "../../Contracts/DataModels";
import TabsBase from "./TabsBase";
import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg";
import CutIcon from "../../../images/notebook/Notebook-cut.svg";
@@ -17,31 +15,25 @@ import SaveIcon from "../../../images/save-cosmos.svg";
import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg";
import InterruptKernelIcon from "../../../images/notebook/Notebook-stop.svg";
import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { Areas, ArmApiVersions } from "../../Common/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { ArmApiVersions } from "../../Common/Constants";
import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
import { NotebookConfigurationUtils } from "../../Utils/NotebookConfigurationUtils";
import { KernelSpecsDisplay, NotebookClientV2 } from "../Notebook/NotebookClientV2";
import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2";
import { configContext } from "../../ConfigContext";
import Explorer from "../Explorer";
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { toJS, stringifyNotebook } from "@nteract/commutable";
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
export interface NotebookTabOptions extends ViewModels.TabOptions {
account: DataModels.DatabaseAccount;
masterKey: string;
container: Explorer;
export interface NotebookTabOptions extends NotebookTabBaseOptions {
notebookContentItem: NotebookContentItem;
}
export default class NotebookTabV2 extends TabsBase {
private static clientManager: NotebookClientV2;
private container: Explorer;
export default class NotebookTabV2 extends NotebookTabBase {
public notebookPath: ko.Observable<string>;
private selectedSparkPool: ko.Observable<string>;
private notebookComponentAdapter: NotebookComponentAdapter;
@@ -50,16 +42,6 @@ export default class NotebookTabV2 extends TabsBase {
super(options);
this.container = options.container;
if (!NotebookTabV2.clientManager) {
NotebookTabV2.clientManager = new NotebookClientV2({
connectionInfo: this.container.notebookServerInfo(),
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
contentProvider: this.container.notebookManager?.notebookContentProvider
});
}
this.notebookPath = ko.observable(options.notebookContentItem.path);
this.container.notebookServerInfo.subscribe((newValue: DataModels.NotebookWorkspaceConnectionInfo) => {
@@ -69,7 +51,7 @@ export default class NotebookTabV2 extends TabsBase {
this.notebookComponentAdapter = new NotebookComponentAdapter({
contentItem: options.notebookContentItem,
notebooksBasePath: this.container.getNotebookBasePath(),
notebookClient: NotebookTabV2.clientManager,
notebookClient: NotebookTabBase.clientManager,
onUpdateKernelInfo: this.onKernelUpdate
});
@@ -115,10 +97,6 @@ export default class NotebookTabV2 extends TabsBase {
return await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName());
}
protected getContainer(): Explorer {
return this.container;
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
@@ -493,12 +471,4 @@ export default class NotebookTabV2 extends TabsBase {
this.container.copyNotebook(notebookContent.name, content);
};
private traceTelemetry(actionType: number) {
TelemetryProcessor.trace(actionType, ActionModifiers.Mark, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
dataExplorerArea: Areas.Notebook
});
}
}

View File

@@ -5,6 +5,7 @@ import SparkMasterTabTemplate from "./SparkMasterTab.html";
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
import TerminalTabTemplate from "./TerminalTab.html";
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
import MongoDocumentsTabV2Template from "./MongoDocumentsTabV2.html";
import MongoQueryTabTemplate from "./MongoQueryTab.html";
import MongoShellTabTemplate from "./MongoShellTab.html";
import QueryTabTemplate from "./QueryTab.html";
@@ -106,6 +107,15 @@ export class MongoQueryTab {
}
}
export class MongoDocumentsTabV2 {
constructor() {
return {
viewModel: TabComponent,
template: MongoDocumentsTabV2Template
};
}
}
export class MongoShellTab {
constructor() {
return {

View File

@@ -24,6 +24,7 @@ import ConflictsTab from "../Tabs/ConflictsTab";
import DocumentsTab from "../Tabs/DocumentsTab";
import GraphTab from "../Tabs/GraphTab";
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
import MongoDocumentsTabV2 from "../Tabs/MongoDocumentsTabV2";
import MongoQueryTab from "../Tabs/MongoQueryTab";
import MongoShellTab from "../Tabs/MongoShellTab";
import QueryTab from "../Tabs/QueryTab";
@@ -506,11 +507,11 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree
});
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
const mongoDocumentsTabs: MongoDocumentsTabV2[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
) as MongoDocumentsTab[];
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
) as MongoDocumentsTabV2[];
let mongoDocumentsTab: MongoDocumentsTabV2 = mongoDocumentsTabs && mongoDocumentsTabs[0];
if (mongoDocumentsTab) {
this.container.tabsManager.activateTab(mongoDocumentsTab);
@@ -525,9 +526,8 @@ export default class Collection implements ViewModels.Collection {
});
this.documentIds([]);
mongoDocumentsTab = new MongoDocumentsTab({
partitionKey: this.partitionKey,
documentIds: this.documentIds,
mongoDocumentsTab = new MongoDocumentsTabV2({
container: this.container,
tabKind: ViewModels.CollectionTabKind.Documents,
title: "Documents",
tabPath: "",