mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 17:23:25 -05:00
Port most notebook model code over to be behind a service (#3068)
- Defines a new NotebookService in Azure Data Studio which will be used to interact with notebooks. Since notebooks can require per-file instantiation the provider is just used to create & track managers for a given URI. - Inject this into notebook.component.ts and pass required parameters that'll be used to properly initialize a manger into the method. Actual initialization not done yet. - Port over & recompile notebook model code - Define most required APIs in sqlops.proposed.d.ts. In the future, these will be used by extensions to contribute their own providers.
This commit is contained in:
@@ -54,10 +54,8 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
|
||||
uri = getNotebookEditorUri(input);
|
||||
if(uri){
|
||||
//TODO: We need to pass in notebook data either through notebook input or notebook service
|
||||
let notebookData: string = fs.readFileSync(uri.fsPath);
|
||||
let fileName: string = input? input.getName() : 'untitled';
|
||||
let filePath: string = uri.fsPath;
|
||||
let notebookInputModel = new NotebookInputModel(filePath, undefined, undefined);
|
||||
let notebookInputModel = new NotebookInputModel(uri, undefined, undefined);
|
||||
//TO DO: Second paramter has to be the content.
|
||||
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
|
||||
return notebookInput;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
|
||||
import { ICellModel } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
@@ -26,6 +25,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
export const CODE_SELECTOR: string = 'code-component';
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ import 'vs/css!./codeCell';
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { CellView, ICellModel } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
|
||||
export const CODE_SELECTOR: string = 'code-cell-component';
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { OnDestroy } from '@angular/core';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export abstract class CellView extends AngularDisposable implements OnDestroy {
|
||||
constructor() {
|
||||
@@ -14,20 +13,3 @@ export abstract class CellView extends AngularDisposable implements OnDestroy {
|
||||
|
||||
public abstract layout(): void;
|
||||
}
|
||||
|
||||
export interface ICellModel {
|
||||
id: string;
|
||||
language: string;
|
||||
source: string;
|
||||
cellType: CellType;
|
||||
active: boolean;
|
||||
cellUri?: URI;
|
||||
}
|
||||
|
||||
export type CellType = 'code' | 'markdown' | 'raw';
|
||||
|
||||
export class CellTypes {
|
||||
public static readonly Code = 'code';
|
||||
public static readonly Markdown = 'markdown';
|
||||
public static readonly Raw = 'raw';
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import 'vs/css!./textCell';
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { CellView, ICellModel } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
export const TEXT_SELECTOR: string = 'text-cell-component';
|
||||
|
||||
|
||||
321
src/sql/parts/notebook/models/cell.ts
Normal file
321
src/sql/parts/notebook/models/cell.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
import { ICellModelOptions, IModelFactory } from './modelInterfaces';
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
|
||||
let modelId = 0;
|
||||
|
||||
|
||||
export class CellModel implements ICellModel {
|
||||
private static LanguageMapping: Map<string, string>;
|
||||
|
||||
private _cellType: nb.CellType;
|
||||
private _source: string;
|
||||
private _language: string;
|
||||
private _future: nb.IFuture;
|
||||
private _outputs: nb.ICellOutput[] = [];
|
||||
private _isEditMode: boolean;
|
||||
private _onOutputsChanged = new Emitter<ReadonlyArray<nb.ICellOutput>>();
|
||||
private _onCellModeChanged = new Emitter<boolean>();
|
||||
public id: string;
|
||||
private _isTrusted: boolean;
|
||||
private _active: boolean;
|
||||
private _cellUri: URI;
|
||||
|
||||
constructor(private factory: IModelFactory, cellData?: nb.ICell, private _options?: ICellModelOptions) {
|
||||
this.id = `${modelId++}`;
|
||||
CellModel.CreateLanguageMappings();
|
||||
// Do nothing for now
|
||||
if (cellData) {
|
||||
this.fromJSON(cellData);
|
||||
} else {
|
||||
this._cellType = CellTypes.Code;
|
||||
this._source = '';
|
||||
}
|
||||
this._isEditMode = this._cellType !== CellTypes.Markdown;
|
||||
this.setDefaultLanguage();
|
||||
if (_options && _options.isTrusted) {
|
||||
this._isTrusted = true;
|
||||
} else {
|
||||
this._isTrusted = false;
|
||||
}
|
||||
}
|
||||
|
||||
public equals(other: ICellModel) {
|
||||
return other && other.id === this.id;
|
||||
}
|
||||
|
||||
public get onOutputsChanged(): Event<ReadonlyArray<nb.ICellOutput>> {
|
||||
return this._onOutputsChanged.event;
|
||||
}
|
||||
|
||||
public get onCellModeChanged(): Event<boolean> {
|
||||
return this._onCellModeChanged.event;
|
||||
}
|
||||
|
||||
public get isEditMode(): boolean {
|
||||
return this._isEditMode;
|
||||
}
|
||||
|
||||
public get future(): nb.IFuture {
|
||||
return this._future;
|
||||
}
|
||||
|
||||
public set isEditMode(isEditMode: boolean) {
|
||||
this._isEditMode = isEditMode;
|
||||
this._onCellModeChanged.fire(this._isEditMode);
|
||||
// Note: this does not require a notebook update as it does not change overall state
|
||||
}
|
||||
|
||||
public get trustedMode(): boolean {
|
||||
return this._isTrusted;
|
||||
}
|
||||
|
||||
public set trustedMode(isTrusted: boolean) {
|
||||
if (this._isTrusted !== isTrusted) {
|
||||
this._isTrusted = isTrusted;
|
||||
this._onOutputsChanged.fire(this._outputs);
|
||||
}
|
||||
}
|
||||
|
||||
public get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
public set active(value: boolean) {
|
||||
this._active = value;
|
||||
}
|
||||
|
||||
public get cellUri(): URI {
|
||||
return this._cellUri;
|
||||
}
|
||||
|
||||
public set cellUri(value: URI) {
|
||||
this._cellUri = value;
|
||||
}
|
||||
|
||||
public get options(): ICellModelOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public get cellType(): CellType {
|
||||
return this._cellType;
|
||||
}
|
||||
|
||||
public get source(): string {
|
||||
return this._source;
|
||||
}
|
||||
|
||||
public set source(newSource: string) {
|
||||
if (this._source !== newSource) {
|
||||
this._source = newSource;
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellSourceUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
public get language(): string {
|
||||
return this._language;
|
||||
}
|
||||
|
||||
public set language(newLanguage: string) {
|
||||
this._language = newLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the future which will be used to update the output
|
||||
* area for this cell
|
||||
*/
|
||||
setFuture(future: nb.IFuture): void {
|
||||
if (this._future === future) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
// Setting the future indicates the cell is running which enables trusted mode.
|
||||
// See https://jupyter-notebook.readthedocs.io/en/stable/security.html
|
||||
|
||||
this._isTrusted = true;
|
||||
|
||||
if (this._future) {
|
||||
this._future.dispose();
|
||||
}
|
||||
this.clearOutputs();
|
||||
this._future = future;
|
||||
future.setReplyHandler({ handle: (msg) => this.handleReply(msg) });
|
||||
future.setIOPubHandler({ handle: (msg) => this.handleIOPub(msg) });
|
||||
}
|
||||
|
||||
private clearOutputs(): void {
|
||||
this._outputs = [];
|
||||
this.fireOutputsChanged();
|
||||
}
|
||||
|
||||
private fireOutputsChanged(): void {
|
||||
this._onOutputsChanged.fire(this.outputs);
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellOutputUpdated);
|
||||
}
|
||||
|
||||
private sendChangeToNotebook(change: NotebookChangeType): void {
|
||||
if (this._options && this._options.notebook) {
|
||||
this._options.notebook.onCellChange(this, change);
|
||||
}
|
||||
}
|
||||
|
||||
public get outputs(): ReadonlyArray<nb.ICellOutput> {
|
||||
return this._outputs;
|
||||
}
|
||||
|
||||
private handleReply(msg: nb.IShellMessage): void {
|
||||
// TODO #931 we should process this. There can be a payload attached which should be added to outputs.
|
||||
// In all other cases, it is a no-op
|
||||
let output: nb.ICellOutput = msg.content as nb.ICellOutput;
|
||||
}
|
||||
|
||||
private handleIOPub(msg: nb.IIOPubMessage): void {
|
||||
let msgType = msg.header.msg_type;
|
||||
let displayId = this.getDisplayId(msg);
|
||||
let output: nb.ICellOutput;
|
||||
switch (msgType) {
|
||||
case 'execute_result':
|
||||
case 'display_data':
|
||||
case 'stream':
|
||||
case 'error':
|
||||
output = msg.content as nb.ICellOutput;
|
||||
output.output_type = msgType;
|
||||
break;
|
||||
case 'clear_output':
|
||||
// TODO wait until next message before clearing
|
||||
// let wait = (msg as KernelMessage.IClearOutputMsg).content.wait;
|
||||
this.clearOutputs();
|
||||
break;
|
||||
case 'update_display_data':
|
||||
output = msg.content as nb.ICellOutput;
|
||||
output.output_type = 'display_data';
|
||||
// TODO #930 handle in-place update of displayed data
|
||||
// targets = this._displayIdMap.get(displayId);
|
||||
// if (targets) {
|
||||
// for (let index of targets) {
|
||||
// model.set(index, output);
|
||||
// }
|
||||
// }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// TODO handle in-place update of displayed data
|
||||
// if (displayId && msgType === 'display_data') {
|
||||
// targets = this._displayIdMap.get(displayId) || [];
|
||||
// targets.push(model.length - 1);
|
||||
// this._displayIdMap.set(displayId, targets);
|
||||
// }
|
||||
if (output) {
|
||||
this._outputs.push(output);
|
||||
this.fireOutputsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private getDisplayId(msg: nb.IIOPubMessage): string | undefined {
|
||||
let transient = (msg.content.transient || {});
|
||||
return transient['display_id'] as string;
|
||||
}
|
||||
|
||||
public toJSON(): nb.ICell {
|
||||
let cellJson: Partial<nb.ICell> = {
|
||||
cell_type: this._cellType,
|
||||
source: this._source,
|
||||
metadata: {
|
||||
}
|
||||
};
|
||||
if (this._cellType === CellTypes.Code) {
|
||||
cellJson.metadata.language = this._language,
|
||||
cellJson.outputs = this._outputs;
|
||||
cellJson.execution_count = 1; // TODO: keep track of actual execution count
|
||||
|
||||
}
|
||||
return cellJson as nb.ICell;
|
||||
}
|
||||
|
||||
public fromJSON(cell: nb.ICell): void {
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
this._cellType = cell.cell_type;
|
||||
this._source = Array.isArray(cell.source) ? cell.source.join('') : cell.source;
|
||||
this._language = (cell.metadata && cell.metadata.language) ? cell.metadata.language : 'python';
|
||||
if (cell.outputs) {
|
||||
for (let output of cell.outputs) {
|
||||
// For now, we're assuming it's OK to save these as-is with no modification
|
||||
this.addOutput(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addOutput(output: nb.ICellOutput) {
|
||||
this._normalize(output);
|
||||
this._outputs.push(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an output.
|
||||
*/
|
||||
private _normalize(value: nb.ICellOutput): void {
|
||||
if (notebookUtils.isStream(value)) {
|
||||
if (Array.isArray(value.text)) {
|
||||
value.text = (value.text as string[]).join('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CreateLanguageMappings(): void {
|
||||
if (CellModel.LanguageMapping) {
|
||||
return;
|
||||
}
|
||||
CellModel.LanguageMapping = new Map<string, string>();
|
||||
CellModel.LanguageMapping['pyspark'] = 'python';
|
||||
CellModel.LanguageMapping['pyspark3'] = 'python';
|
||||
CellModel.LanguageMapping['python'] = 'python';
|
||||
CellModel.LanguageMapping['scala'] = 'scala';
|
||||
}
|
||||
|
||||
private get languageInfo(): nb.ILanguageInfo {
|
||||
if (this._options && this._options.notebook && this._options.notebook.languageInfo) {
|
||||
return this._options.notebook.languageInfo;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private setDefaultLanguage(): void {
|
||||
this._language = 'python';
|
||||
// In languageInfo, set the language to the "name" property
|
||||
// If the "name" property isn't defined, check the "mimeType" property
|
||||
// Otherwise, default to python as the language
|
||||
let languageInfo = this.languageInfo;
|
||||
if (languageInfo) {
|
||||
if (languageInfo.name) {
|
||||
// check the LanguageMapping to determine if a mapping is necessary (example 'pyspark' -> 'python')
|
||||
if (CellModel.LanguageMapping[languageInfo.name]) {
|
||||
this._language = CellModel.LanguageMapping[languageInfo.name];
|
||||
} else {
|
||||
this._language = languageInfo.name;
|
||||
}
|
||||
} else if (languageInfo.mimetype) {
|
||||
this._language = languageInfo.mimetype;
|
||||
}
|
||||
}
|
||||
let mimeTypePrefix = 'x-';
|
||||
if (this._language.includes(mimeTypePrefix)) {
|
||||
this._language = this._language.replace(mimeTypePrefix, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
360
src/sql/parts/notebook/models/clientSession.ts
Normal file
360
src/sql/parts/notebook/models/clientSession.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// This code is based on @jupyterlab/packages/apputils/src/clientsession.tsx
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { IClientSession, IKernelPreference, IClientSessionOptions } from './modelInterfaces';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import * as sparkUtils from '../spark/sparkUtils';
|
||||
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||
|
||||
/**
|
||||
* Implementation of a client session. This is a model over session operations,
|
||||
* which may come from the session manager or a specific session.
|
||||
*/
|
||||
export class ClientSession implements IClientSession {
|
||||
//#region private fields with public accessors
|
||||
private _terminatedEmitter = new Emitter<void>();
|
||||
private _kernelChangedEmitter = new Emitter<nb.IKernelChangedArgs>();
|
||||
private _statusChangedEmitter = new Emitter<nb.ISession>();
|
||||
private _iopubMessageEmitter = new Emitter<nb.IMessage>();
|
||||
private _unhandledMessageEmitter = new Emitter<nb.IMessage>();
|
||||
private _propertyChangedEmitter = new Emitter<'path' | 'name' | 'type'>();
|
||||
private _path: string;
|
||||
private _type: string;
|
||||
private _name: string;
|
||||
private _isReady: boolean;
|
||||
private _ready: Deferred<void>;
|
||||
private _kernelChangeCompleted: Deferred<void>;
|
||||
private _kernelPreference: IKernelPreference;
|
||||
private _kernelDisplayName: string;
|
||||
private _errorMessage: string;
|
||||
//#endregion
|
||||
|
||||
private _serverLoadFinished: Promise<void>;
|
||||
private _session: nb.ISession;
|
||||
private isServerStarted: boolean;
|
||||
private notebookManager: INotebookManager;
|
||||
private _connection: NotebookConnection;
|
||||
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
|
||||
|
||||
constructor(private options: IClientSessionOptions) {
|
||||
this._path = options.path;
|
||||
this.notebookManager = options.notebookManager;
|
||||
this._isReady = false;
|
||||
this._ready = new Deferred<void>();
|
||||
this._kernelChangeCompleted = new Deferred<void>();
|
||||
}
|
||||
|
||||
public async initialize(connection?: NotebookConnection): Promise<void> {
|
||||
try {
|
||||
this._kernelConfigActions.push((kernelName: string) => { return this.runTasksBeforeSessionStart(kernelName); });
|
||||
this._connection = connection;
|
||||
this._serverLoadFinished = this.startServer();
|
||||
await this._serverLoadFinished;
|
||||
await this.initializeSession();
|
||||
} catch (err) {
|
||||
this._errorMessage = notebookUtils.getErrorMessage(err);
|
||||
}
|
||||
// Always resolving for now. It's up to callers to check for error case
|
||||
this._isReady = true;
|
||||
this._ready.resolve();
|
||||
this._kernelChangeCompleted.resolve();
|
||||
}
|
||||
|
||||
private async startServer(): Promise<void> {
|
||||
let serverManager = this.notebookManager.serverManager;
|
||||
if (serverManager && !serverManager.isStarted) {
|
||||
await serverManager.startServer();
|
||||
if (!serverManager.isStarted) {
|
||||
throw new Error(nls.localize('ServerNotStarted', 'Server did not start for unknown reason'));
|
||||
}
|
||||
this.isServerStarted = serverManager.isStarted;
|
||||
} else {
|
||||
this.isServerStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeSession(): Promise<void> {
|
||||
await this._serverLoadFinished;
|
||||
if (this.isServerStarted) {
|
||||
if (!this.notebookManager.sessionManager.isReady) {
|
||||
await this.notebookManager.sessionManager.ready;
|
||||
}
|
||||
if (this._kernelPreference && this._kernelPreference.shouldStart) {
|
||||
await this.startSessionInstance(this._kernelPreference.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async startSessionInstance(kernelName: string): Promise<void> {
|
||||
let session: nb.ISession;
|
||||
try {
|
||||
session = await this.notebookManager.sessionManager.startNew({
|
||||
path: this.path,
|
||||
kernelName: kernelName
|
||||
// TODO add kernel name if saved in the document
|
||||
});
|
||||
session.defaultKernelLoaded = true;
|
||||
} catch (err) {
|
||||
// TODO move registration
|
||||
if (err && err.response && err.response.status === 501) {
|
||||
this.options.notificationService.warn(nls.localize('sparkKernelRequiresConnection', 'Kernel {0} was not found. The default kernel will be used instead.', kernelName));
|
||||
session = await this.notebookManager.sessionManager.startNew({
|
||||
path: this.path,
|
||||
kernelName: undefined
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
session.defaultKernelLoaded = false;
|
||||
}
|
||||
this._session = session;
|
||||
await this.runKernelConfigActions(kernelName);
|
||||
this._statusChangedEmitter.fire(session);
|
||||
}
|
||||
|
||||
private async runKernelConfigActions(kernelName: string): Promise<void> {
|
||||
for (let startAction of this._kernelConfigActions) {
|
||||
await startAction(kernelName);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
// No-op for now
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the server has finished loading. It may have failed to load in
|
||||
* which case the view will be in an error state.
|
||||
*/
|
||||
public get serverLoadFinished(): Promise<void> {
|
||||
return this._serverLoadFinished;
|
||||
}
|
||||
|
||||
|
||||
//#region IClientSession Properties
|
||||
public get terminated(): Event<void> {
|
||||
return this._terminatedEmitter.event;
|
||||
}
|
||||
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||
return this._kernelChangedEmitter.event;
|
||||
}
|
||||
public get statusChanged(): Event<nb.ISession> {
|
||||
return this._statusChangedEmitter.event;
|
||||
}
|
||||
public get iopubMessage(): Event<nb.IMessage> {
|
||||
return this._iopubMessageEmitter.event;
|
||||
}
|
||||
public get unhandledMessage(): Event<nb.IMessage> {
|
||||
return this._unhandledMessageEmitter.event;
|
||||
}
|
||||
public get propertyChanged(): Event<'path' | 'name' | 'type'> {
|
||||
return this._propertyChangedEmitter.event;
|
||||
}
|
||||
public get kernel(): nb.IKernel | null {
|
||||
return this._session ? this._session.kernel : undefined;
|
||||
}
|
||||
public get path(): string {
|
||||
return this._path;
|
||||
}
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
public get type(): string {
|
||||
return this._type;
|
||||
}
|
||||
public get status(): nb.KernelStatus {
|
||||
if (!this.isReady) {
|
||||
return 'starting';
|
||||
}
|
||||
return this._session ? this._session.status : 'dead';
|
||||
}
|
||||
public get isReady(): boolean {
|
||||
return this._isReady;
|
||||
}
|
||||
public get ready(): Promise<void> {
|
||||
return this._ready.promise;
|
||||
}
|
||||
public get kernelChangeCompleted(): Promise<void> {
|
||||
return this._kernelChangeCompleted.promise;
|
||||
}
|
||||
public get kernelPreference(): IKernelPreference {
|
||||
return this._kernelPreference;
|
||||
}
|
||||
public set kernelPreference(value: IKernelPreference) {
|
||||
this._kernelPreference = value;
|
||||
}
|
||||
public get kernelDisplayName(): string {
|
||||
return this._kernelDisplayName;
|
||||
}
|
||||
public get errorMessage(): string {
|
||||
return this._errorMessage;
|
||||
}
|
||||
public get isInErrorState(): boolean {
|
||||
return !!this._errorMessage;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Not Yet Implemented
|
||||
/**
|
||||
* Change the current kernel associated with the document.
|
||||
*/
|
||||
async changeKernel(options: nb.IKernelSpec): Promise<nb.IKernel> {
|
||||
this._kernelChangeCompleted = new Deferred<void>();
|
||||
this._isReady = false;
|
||||
let oldKernel = this.kernel;
|
||||
let newKernel = this.kernel;
|
||||
|
||||
let kernel = await this.doChangeKernel(options);
|
||||
try {
|
||||
await kernel.ready;
|
||||
} catch (error) {
|
||||
// Cleanup some state before re-throwing
|
||||
this._isReady = kernel.isReady;
|
||||
this._kernelChangeCompleted.resolve();
|
||||
throw error;
|
||||
}
|
||||
newKernel = this._session ? kernel : this._session.kernel;
|
||||
this._isReady = kernel.isReady;
|
||||
// Send resolution events to listeners
|
||||
this._kernelChangeCompleted.resolve();
|
||||
this._kernelChangedEmitter.fire({
|
||||
oldValue: oldKernel,
|
||||
newValue: newKernel
|
||||
});
|
||||
return kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to either call ChangeKernel on current session, or start a new session
|
||||
* @param options
|
||||
*/
|
||||
private async doChangeKernel(options: nb.IKernelSpec): Promise<nb.IKernel> {
|
||||
let kernel: nb.IKernel;
|
||||
if (this._session) {
|
||||
kernel = await this._session.changeKernel(options);
|
||||
await this.runKernelConfigActions(kernel.name);
|
||||
} else {
|
||||
kernel = await this.startSessionInstance(options.name).then(() => this.kernel);
|
||||
}
|
||||
return kernel;
|
||||
}
|
||||
|
||||
public async runTasksBeforeSessionStart(kernelName: string): Promise<void> {
|
||||
// TODO we should move all Spark-related code to SparkMagicContext
|
||||
if (this._session && this._connection && this.isSparkKernel(kernelName)) {
|
||||
// TODO may need to reenable a way to get the credential
|
||||
// await this._connection.getCredential();
|
||||
// %_do_not_call_change_endpoint is a SparkMagic command that lets users change endpoint options,
|
||||
// such as user/profile/host name/auth type
|
||||
|
||||
let server = URI.parse(sparkUtils.getLivyUrl(this._connection.host, this._connection.knoxport)).toString();
|
||||
let doNotCallChangeEndpointParams =
|
||||
`%_do_not_call_change_endpoint --username=${this._connection.user} --password=${this._connection.password} --server=${server} --auth=Basic_Access`;
|
||||
let future = this._session.kernel.requestExecute({
|
||||
code: doNotCallChangeEndpointParams
|
||||
}, true);
|
||||
await future.done;
|
||||
}
|
||||
}
|
||||
|
||||
public async updateConnection(connection: NotebookConnection): Promise<void> {
|
||||
if (!this.kernel) {
|
||||
// TODO is there any case where skipping causes errors? Do far it seems like it gets called twice
|
||||
return;
|
||||
}
|
||||
this._connection = (connection.connectionProfile.id !== '-1') ? connection : this._connection;
|
||||
// if kernel is not set, don't run kernel config actions
|
||||
// this should only occur when a cell is cancelled, which interrupts the kernel
|
||||
if (this.kernel && this.kernel.name) {
|
||||
await this.runKernelConfigActions(this.kernel.name);
|
||||
}
|
||||
}
|
||||
|
||||
isSparkKernel(kernelName: string): any {
|
||||
return kernelName && kernelName.toLowerCase().indexOf('spark') > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the kernel and shutdown the session.
|
||||
*
|
||||
* @returns A promise that resolves when the session is shut down.
|
||||
*/
|
||||
public async shutdown(): Promise<void> {
|
||||
// Always try to shut down session
|
||||
if (this._session && this._session.id) {
|
||||
this.notebookManager.sessionManager.shutdown(this._session.id);
|
||||
}
|
||||
let serverManager = this.notebookManager.serverManager;
|
||||
if (serverManager) {
|
||||
await serverManager.stopServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a kernel for the session.
|
||||
*/
|
||||
selectKernel(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the session.
|
||||
*
|
||||
* @returns A promise that resolves with whether the kernel has restarted.
|
||||
*
|
||||
* #### Notes
|
||||
* If there is a running kernel, present a dialog.
|
||||
* If there is no kernel, we start a kernel with the last run
|
||||
* kernel name and resolves with `true`. If no kernel has been started,
|
||||
* this is a no-op, and resolves with `false`.
|
||||
*/
|
||||
restart(): Promise<boolean> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the session path.
|
||||
*
|
||||
* @param path - The new session path.
|
||||
*
|
||||
* @returns A promise that resolves when the session has renamed.
|
||||
*
|
||||
* #### Notes
|
||||
* This uses the Jupyter REST API, and the response is validated.
|
||||
* The promise is fulfilled on a valid response and rejected otherwise.
|
||||
*/
|
||||
setPath(path: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the session name.
|
||||
*/
|
||||
setName(name: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the session type.
|
||||
*/
|
||||
setType(type: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
47
src/sql/parts/notebook/models/contracts.ts
Normal file
47
src/sql/parts/notebook/models/contracts.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type CellType = 'code' | 'markdown' | 'raw';
|
||||
|
||||
export class CellTypes {
|
||||
public static readonly Code = 'code';
|
||||
public static readonly Markdown = 'markdown';
|
||||
public static readonly Raw = 'raw';
|
||||
}
|
||||
|
||||
// to do: add all mime types
|
||||
export type MimeType = 'text/plain' | 'text/html';
|
||||
|
||||
// to do: add all mime types
|
||||
export class MimeTypes {
|
||||
public static readonly PlainText = 'text/plain';
|
||||
public static readonly HTML = 'text/html';
|
||||
}
|
||||
|
||||
export type OutputType =
|
||||
| 'execute_result'
|
||||
| 'display_data'
|
||||
| 'stream'
|
||||
| 'error'
|
||||
| 'update_display_data';
|
||||
|
||||
export class OutputTypes {
|
||||
public static readonly ExecuteResult = 'execute_result';
|
||||
public static readonly DisplayData = 'display_data';
|
||||
public static readonly Stream = 'stream';
|
||||
public static readonly Error = 'error';
|
||||
public static readonly UpdateDisplayData = 'update_display_data';
|
||||
}
|
||||
|
||||
export enum NotebookChangeType {
|
||||
CellsAdded,
|
||||
CellDeleted,
|
||||
CellSourceUpdated,
|
||||
CellOutputUpdated,
|
||||
DirtyStateChanged
|
||||
}
|
||||
23
src/sql/parts/notebook/models/modelFactory.ts
Normal file
23
src/sql/parts/notebook/models/modelFactory.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import { CellModel } from './cell';
|
||||
import { IClientSession, IClientSessionOptions, ICellModelOptions, ICellModel, IModelFactory } from './modelInterfaces';
|
||||
import { ClientSession } from './clientSession';
|
||||
|
||||
export class ModelFactory implements IModelFactory {
|
||||
|
||||
public createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel {
|
||||
return new CellModel(this, cell, options);
|
||||
}
|
||||
|
||||
public createClientSession(options: IClientSessionOptions): IClientSession {
|
||||
return new ClientSession(options);
|
||||
}
|
||||
}
|
||||
372
src/sql/parts/notebook/models/modelInterfaces.ts
Normal file
372
src/sql/parts/notebook/models/modelInterfaces.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// This code is based on @jupyterlab/packages/apputils/src/clientsession.tsx
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
import { CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
||||
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
|
||||
export interface IClientSessionOptions {
|
||||
path: string;
|
||||
notebookManager: INotebookManager;
|
||||
notificationService: INotificationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface of client session object.
|
||||
*
|
||||
* The client session represents the link between
|
||||
* a path and its kernel for the duration of the lifetime
|
||||
* of the session object. The session can have no current
|
||||
* kernel, and can start a new kernel at any time.
|
||||
*/
|
||||
export interface IClientSession extends IDisposable {
|
||||
/**
|
||||
* A signal emitted when the session is shut down.
|
||||
*/
|
||||
readonly terminated: Event<void>;
|
||||
|
||||
/**
|
||||
* A signal emitted when the kernel changes.
|
||||
*/
|
||||
readonly kernelChanged: Event<nb.IKernelChangedArgs>;
|
||||
|
||||
/**
|
||||
* A signal emitted when the kernel status changes.
|
||||
*/
|
||||
readonly statusChanged: Event<nb.ISession>;
|
||||
|
||||
/**
|
||||
* A signal emitted for a kernel messages.
|
||||
*/
|
||||
readonly iopubMessage: Event<nb.IMessage>;
|
||||
|
||||
/**
|
||||
* A signal emitted for an unhandled kernel message.
|
||||
*/
|
||||
readonly unhandledMessage: Event<nb.IMessage>;
|
||||
|
||||
/**
|
||||
* A signal emitted when a session property changes.
|
||||
*/
|
||||
readonly propertyChanged: Event<'path' | 'name' | 'type'>;
|
||||
|
||||
/**
|
||||
* The current kernel associated with the document.
|
||||
*/
|
||||
readonly kernel: nb.IKernel | null;
|
||||
|
||||
/**
|
||||
* The current path associated with the client session.
|
||||
*/
|
||||
readonly path: string;
|
||||
|
||||
/**
|
||||
* The current name associated with the client session.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* The type of the client session.
|
||||
*/
|
||||
readonly type: string;
|
||||
|
||||
/**
|
||||
* The current status of the client session.
|
||||
*/
|
||||
readonly status: nb.KernelStatus;
|
||||
|
||||
/**
|
||||
* Whether the session is ready.
|
||||
*/
|
||||
readonly isReady: boolean;
|
||||
|
||||
/**
|
||||
* Whether the session is in an unusable state
|
||||
*/
|
||||
readonly isInErrorState: boolean;
|
||||
/**
|
||||
* The error information, if this session is in an error state
|
||||
*/
|
||||
readonly errorMessage: string;
|
||||
|
||||
/**
|
||||
* A promise that is fulfilled when the session is ready.
|
||||
*/
|
||||
readonly ready: Promise<void>;
|
||||
|
||||
/**
|
||||
* A promise that is fulfilled when the session completes a kernel change.
|
||||
*/
|
||||
readonly kernelChangeCompleted: Promise<void>;
|
||||
|
||||
/**
|
||||
* The kernel preference.
|
||||
*/
|
||||
kernelPreference: IKernelPreference;
|
||||
|
||||
/**
|
||||
* The display name of the kernel.
|
||||
*/
|
||||
readonly kernelDisplayName: string;
|
||||
|
||||
/**
|
||||
* Initializes the ClientSession, by starting the server and
|
||||
* connecting to the SessionManager.
|
||||
* This will optionally start a session if the kernel preferences
|
||||
* indicate this is desired
|
||||
*/
|
||||
initialize(connection?: NotebookConnection): Promise<void>;
|
||||
|
||||
/**
|
||||
* Change the current kernel associated with the document.
|
||||
*/
|
||||
changeKernel(
|
||||
options: nb.IKernelSpec
|
||||
): Promise<nb.IKernel>;
|
||||
|
||||
/**
|
||||
* Kill the kernel and shutdown the session.
|
||||
*
|
||||
* @returns A promise that resolves when the session is shut down.
|
||||
*/
|
||||
shutdown(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Select a kernel for the session.
|
||||
*/
|
||||
selectKernel(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Restart the session.
|
||||
*
|
||||
* @returns A promise that resolves with whether the kernel has restarted.
|
||||
*
|
||||
* #### Notes
|
||||
* If there is a running kernel, present a dialog.
|
||||
* If there is no kernel, we start a kernel with the last run
|
||||
* kernel name and resolves with `true`. If no kernel has been started,
|
||||
* this is a no-op, and resolves with `false`.
|
||||
*/
|
||||
restart(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Change the session path.
|
||||
*
|
||||
* @param path - The new session path.
|
||||
*
|
||||
* @returns A promise that resolves when the session has renamed.
|
||||
*
|
||||
* #### Notes
|
||||
* This uses the Jupyter REST API, and the response is validated.
|
||||
* The promise is fulfilled on a valid response and rejected otherwise.
|
||||
*/
|
||||
setPath(path: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Change the session name.
|
||||
*/
|
||||
setName(name: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Change the session type.
|
||||
*/
|
||||
setType(type: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates the connection
|
||||
*/
|
||||
updateConnection(connection: NotebookConnection): void;
|
||||
}
|
||||
|
||||
export interface IDefaultConnection {
|
||||
defaultConnection: IConnectionProfile;
|
||||
otherConnections: IConnectionProfile[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A kernel preference.
|
||||
*/
|
||||
export interface IKernelPreference {
|
||||
/**
|
||||
* The name of the kernel.
|
||||
*/
|
||||
readonly name?: string;
|
||||
|
||||
/**
|
||||
* The preferred kernel language.
|
||||
*/
|
||||
readonly language?: string;
|
||||
|
||||
/**
|
||||
* The id of an existing kernel.
|
||||
*/
|
||||
readonly id?: string;
|
||||
|
||||
/**
|
||||
* Whether to prefer starting a kernel.
|
||||
*/
|
||||
readonly shouldStart?: boolean;
|
||||
|
||||
/**
|
||||
* Whether a kernel can be started.
|
||||
*/
|
||||
readonly canStart?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to auto-start the default kernel if no matching kernel is found.
|
||||
*/
|
||||
readonly autoStartDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface INotebookModel {
|
||||
/**
|
||||
* Client Session in the notebook, used for sending requests to the notebook service
|
||||
*/
|
||||
readonly clientSession: IClientSession;
|
||||
/**
|
||||
* LanguageInfo saved in the query book
|
||||
*/
|
||||
readonly languageInfo: nb.ILanguageInfo;
|
||||
|
||||
/**
|
||||
* The notebook service used to call backend APIs
|
||||
*/
|
||||
readonly notebookManager: INotebookManager;
|
||||
|
||||
/**
|
||||
* Event fired on first initialization of the kernel and
|
||||
* on subsequent change events
|
||||
*/
|
||||
readonly kernelChanged: Event<nb.IKernelChangedArgs>;
|
||||
|
||||
/**
|
||||
* Event fired on first initialization of the kernels and
|
||||
* on subsequent change events
|
||||
*/
|
||||
readonly kernelsChanged: Event<nb.IKernelSpec>;
|
||||
|
||||
/**
|
||||
* Default kernel
|
||||
*/
|
||||
defaultKernel?: nb.IKernelSpec;
|
||||
|
||||
/**
|
||||
* Event fired on first initialization of the contexts and
|
||||
* on subsequent change events
|
||||
*/
|
||||
readonly contextsChanged: Event<void>;
|
||||
|
||||
/**
|
||||
* The specs for available kernels, or undefined if these have
|
||||
* not been loaded yet
|
||||
*/
|
||||
readonly specs: nb.IAllKernels | undefined;
|
||||
|
||||
/**
|
||||
* The specs for available contexts, or undefined if these have
|
||||
* not been loaded yet
|
||||
*/
|
||||
readonly contexts: IDefaultConnection | undefined;
|
||||
|
||||
/**
|
||||
* The trusted mode of the NoteBook
|
||||
*/
|
||||
trustedMode: boolean;
|
||||
|
||||
/**
|
||||
* Change the current kernel from the Kernel dropdown
|
||||
* @param displayName kernel name (as displayed in Kernel dropdown)
|
||||
*/
|
||||
changeKernel(displayName: string): void;
|
||||
|
||||
/**
|
||||
* Change the current context (if applicable)
|
||||
*/
|
||||
changeContext(host: string): void;
|
||||
|
||||
/**
|
||||
* Adds a cell to the end of the model
|
||||
*/
|
||||
addCell(cellType: CellType): void;
|
||||
|
||||
/**
|
||||
* Deletes a cell
|
||||
*/
|
||||
deleteCell(cellModel: ICellModel): void;
|
||||
|
||||
/**
|
||||
* Save the model to its backing content manager.
|
||||
* Serializes the model and then calls through to save it
|
||||
*/
|
||||
saveModel(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Notifies the notebook of a change in the cell
|
||||
*/
|
||||
onCellChange(cell: ICellModel, change: NotebookChangeType): void;
|
||||
}
|
||||
|
||||
export interface ICellModelOptions {
|
||||
notebook: INotebookModel;
|
||||
isTrusted: boolean;
|
||||
}
|
||||
|
||||
export interface ICellModel {
|
||||
cellUri: URI;
|
||||
id: string;
|
||||
language: string;
|
||||
source: string;
|
||||
cellType: CellType;
|
||||
trustedMode: boolean;
|
||||
active: boolean;
|
||||
equals(cellModel: ICellModel): boolean;
|
||||
toJSON(): nb.ICell;
|
||||
}
|
||||
|
||||
export interface IModelFactory {
|
||||
|
||||
createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel;
|
||||
createClientSession(options: IClientSessionOptions): IClientSession;
|
||||
}
|
||||
|
||||
|
||||
export interface INotebookModelOptions {
|
||||
/**
|
||||
* Path to the local or remote notebook
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Factory for creating cells and client sessions
|
||||
*/
|
||||
factory: IModelFactory;
|
||||
|
||||
notebookManager: INotebookManager;
|
||||
|
||||
notificationService: INotificationService;
|
||||
connectionService: IConnectionManagementService;
|
||||
}
|
||||
|
||||
// TODO would like to move most of these constants to an extension
|
||||
export namespace notebookConstants {
|
||||
export const hadoopKnoxProviderName = 'HADOOP_KNOX';
|
||||
export const python3 = 'python3';
|
||||
export const python3DisplayName = 'Python 3';
|
||||
export const defaultSparkKernel = 'pyspark3kernel';
|
||||
|
||||
}
|
||||
94
src/sql/parts/notebook/models/notebookConnection.ts
Normal file
94
src/sql/parts/notebook/models/notebookConnection.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
|
||||
export namespace constants {
|
||||
export const hostPropName = 'host';
|
||||
export const userPropName = 'user';
|
||||
export const knoxPortPropName = 'knoxport';
|
||||
export const clusterPropName = 'clustername';
|
||||
export const passwordPropName = 'password';
|
||||
export const defaultKnoxPort = '30443';
|
||||
}
|
||||
/**
|
||||
* This is a temporary connection definition, with known properties for Knox gateway connections.
|
||||
* Long term this should be refactored to an extension contribution
|
||||
*
|
||||
* @export
|
||||
* @class NotebookConnection
|
||||
*/
|
||||
export class NotebookConnection {
|
||||
private _host: string;
|
||||
private _knoxPort: string;
|
||||
|
||||
constructor(private _connectionProfile: IConnectionProfile) {
|
||||
if (!this._connectionProfile) {
|
||||
throw new Error(localize('connectionInfoMissing', 'connectionInfo is required'));
|
||||
}
|
||||
}
|
||||
|
||||
public get connectionProfile(): IConnectionProfile {
|
||||
return this.connectionProfile;
|
||||
}
|
||||
|
||||
|
||||
public get host(): string {
|
||||
if (!this._host) {
|
||||
this.ensureHostAndPort();
|
||||
}
|
||||
return this._host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets host and port values, using any ',' or ':' delimited port in the hostname in
|
||||
* preference to the built in port.
|
||||
*/
|
||||
private ensureHostAndPort(): void {
|
||||
this._host = this.connectionProfile.options[constants.hostPropName];
|
||||
this._knoxPort = NotebookConnection.getKnoxPortOrDefault(this.connectionProfile);
|
||||
// determine whether the host has either a ',' or ':' in it
|
||||
this.setHostAndPort(',');
|
||||
this.setHostAndPort(':');
|
||||
}
|
||||
|
||||
// set port and host correctly after we've identified that a delimiter exists in the host name
|
||||
private setHostAndPort(delimeter: string): void {
|
||||
let originalHost = this._host;
|
||||
let index = originalHost.indexOf(delimeter);
|
||||
if (index > -1) {
|
||||
this._host = originalHost.slice(0, index);
|
||||
this._knoxPort = originalHost.slice(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public get user(): string {
|
||||
return this.connectionProfile.options[constants.userPropName];
|
||||
}
|
||||
|
||||
public get password(): string {
|
||||
return this.connectionProfile.options[constants.passwordPropName];
|
||||
}
|
||||
|
||||
public get knoxport(): string {
|
||||
if (!this._knoxPort) {
|
||||
this.ensureHostAndPort();
|
||||
}
|
||||
return this._knoxPort;
|
||||
}
|
||||
|
||||
private static getKnoxPortOrDefault(connectionProfile: IConnectionProfile): string {
|
||||
let port = connectionProfile.options[constants.knoxPortPropName];
|
||||
if (!port) {
|
||||
port = constants.defaultKnoxPort;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
}
|
||||
474
src/sql/parts/notebook/models/notebookModel.ts
Normal file
474
src/sql/parts/notebook/models/notebookModel.ts
Normal file
@@ -0,0 +1,474 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { CellModel } from './cell';
|
||||
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants } from './modelInterfaces';
|
||||
import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { nbversion } from '../notebookConstants';
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import { SparkMagicContexts } from 'sql/parts/notebook/models/sparkMagicContexts';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||
|
||||
/*
|
||||
* Used to control whether a message in a dialog/wizard is displayed as an error,
|
||||
* warning, or informational message. Default is error.
|
||||
*/
|
||||
export enum MessageLevel {
|
||||
Error = 0,
|
||||
Warning = 1,
|
||||
Information = 2
|
||||
}
|
||||
|
||||
export class ErrorInfo {
|
||||
constructor(public readonly message: string, public readonly severity: MessageLevel) {
|
||||
}
|
||||
}
|
||||
export interface NotebookContentChange {
|
||||
/**
|
||||
* What was the change that occurred?
|
||||
*/
|
||||
changeType: NotebookChangeType;
|
||||
/**
|
||||
* Optional cells that were changed
|
||||
*/
|
||||
cells?: ICellModel | ICellModel[];
|
||||
/**
|
||||
* Optional index of the change, indicating the cell at which an insert or
|
||||
* delete occurred
|
||||
*/
|
||||
cellIndex?: number;
|
||||
/**
|
||||
* Optional value indicating if the notebook is in a dirty or clean state after this change
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof NotebookContentChange
|
||||
*/
|
||||
isDirty?: boolean;
|
||||
}
|
||||
|
||||
export class NotebookModel extends Disposable implements INotebookModel {
|
||||
private _contextsChangedEmitter = new Emitter<void>();
|
||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||
private _kernelsChangedEmitter = new Emitter<nb.IKernelSpec>();
|
||||
private _inErrorState: boolean = false;
|
||||
private _clientSession: IClientSession;
|
||||
private _sessionLoadFinished: Promise<void>;
|
||||
private _onClientSessionReady = new Emitter<IClientSession>();
|
||||
private _activeContexts: IDefaultConnection;
|
||||
private _trustedMode: boolean;
|
||||
|
||||
private _cells: ICellModel[];
|
||||
private _defaultLanguageInfo: nb.ILanguageInfo;
|
||||
private onErrorEmitter = new Emitter<ErrorInfo>();
|
||||
private _savedKernelInfo: nb.IKernelInfo;
|
||||
private readonly _nbformat: number = nbversion.MAJOR_VERSION;
|
||||
private readonly _nbformatMinor: number = nbversion.MINOR_VERSION;
|
||||
private _hadoopConnection: NotebookConnection;
|
||||
private _defaultKernel: nb.IKernelSpec;
|
||||
|
||||
constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) {
|
||||
super();
|
||||
if (!notebookOptions || !notebookOptions.path || !notebookOptions.notebookManager) {
|
||||
throw new Error('path or notebook service not defined');
|
||||
}
|
||||
if (startSessionImmediately) {
|
||||
this.backgroundStartSession();
|
||||
}
|
||||
this._trustedMode = false;
|
||||
}
|
||||
|
||||
public get notebookManager(): INotebookManager {
|
||||
return this.notebookOptions.notebookManager;
|
||||
}
|
||||
|
||||
public get hasServerManager(): boolean {
|
||||
// If the service has a server manager, then we can show the start button
|
||||
return !!this.notebookManager.serverManager;
|
||||
}
|
||||
|
||||
public get contentChanged(): Event<NotebookContentChange> {
|
||||
return this._contentChangedEmitter.event;
|
||||
}
|
||||
|
||||
public get isSessionReady(): boolean {
|
||||
return !!this._clientSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClientSession object which handles management of a session instance,
|
||||
* plus startup of the session manager which can return key metadata about the
|
||||
* notebook environment
|
||||
*/
|
||||
public get clientSession(): IClientSession {
|
||||
return this._clientSession;
|
||||
}
|
||||
|
||||
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||
return this.clientSession.kernelChanged;
|
||||
}
|
||||
|
||||
public get kernelsChanged(): Event<nb.IKernelSpec> {
|
||||
return this._kernelsChangedEmitter.event;
|
||||
}
|
||||
|
||||
public get defaultKernel(): nb.IKernelSpec {
|
||||
return this._defaultKernel;
|
||||
}
|
||||
|
||||
public get contextsChanged(): Event<void> {
|
||||
return this._contextsChangedEmitter.event;
|
||||
}
|
||||
|
||||
public get cells(): ICellModel[] {
|
||||
return this._cells;
|
||||
}
|
||||
|
||||
public get contexts(): IDefaultConnection {
|
||||
return this._activeContexts;
|
||||
}
|
||||
|
||||
public get specs(): nb.IAllKernels | undefined {
|
||||
return this.notebookManager.sessionManager.specs;
|
||||
}
|
||||
|
||||
public get inErrorState(): boolean {
|
||||
return this._inErrorState;
|
||||
}
|
||||
|
||||
public get onError(): Event<ErrorInfo> {
|
||||
return this.onErrorEmitter.event;
|
||||
}
|
||||
|
||||
public get trustedMode(): boolean {
|
||||
return this._trustedMode;
|
||||
}
|
||||
|
||||
public set trustedMode(isTrusted: boolean) {
|
||||
this._trustedMode = isTrusted;
|
||||
if (this._cells) {
|
||||
this._cells.forEach(c => {
|
||||
c.trustedMode = this._trustedMode;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the server has finished loading. It may have failed to load in
|
||||
* which case the view will be in an error state.
|
||||
*/
|
||||
public get sessionLoadFinished(): Promise<void> {
|
||||
return this._sessionLoadFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies when the client session is ready for use
|
||||
*/
|
||||
public get onClientSessionReady(): Event<IClientSession> {
|
||||
return this._onClientSessionReady.event;
|
||||
}
|
||||
|
||||
public async requestModelLoad(isTrusted: boolean = false): Promise<void> {
|
||||
try {
|
||||
this._trustedMode = isTrusted;
|
||||
let contents = await this.notebookManager.contentManager.getNotebookContents(this.notebookOptions.path);
|
||||
let factory = this.notebookOptions.factory;
|
||||
// if cells already exist, create them with language info (if it is saved)
|
||||
this._cells = undefined;
|
||||
if (contents) {
|
||||
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
|
||||
this._savedKernelInfo = this.getSavedKernelInfo(contents);
|
||||
if (contents.cells && contents.cells.length > 0) {
|
||||
this._cells = contents.cells.map(c => factory.createCell(c, { notebook: this, isTrusted: isTrusted }));
|
||||
}
|
||||
}
|
||||
if (!this._cells) {
|
||||
this._cells = [this.createCell(CellTypes.Code)];
|
||||
}
|
||||
} catch (error) {
|
||||
this._inErrorState = true;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
addCell(cellType: CellType): void {
|
||||
if (this.inErrorState || !this._cells) {
|
||||
return;
|
||||
}
|
||||
let cell = this.createCell(cellType);
|
||||
this._cells.push(cell);
|
||||
this._contentChangedEmitter.fire({
|
||||
changeType: NotebookChangeType.CellsAdded,
|
||||
cells: [cell]
|
||||
});
|
||||
}
|
||||
|
||||
private createCell(cellType: CellType): ICellModel {
|
||||
let singleCell: nb.ICell = {
|
||||
cell_type: cellType,
|
||||
source: '',
|
||||
metadata: {},
|
||||
execution_count: 1
|
||||
};
|
||||
return this.notebookOptions.factory.createCell(singleCell, { notebook: this, isTrusted: true });
|
||||
}
|
||||
|
||||
deleteCell(cellModel: CellModel): void {
|
||||
if (this.inErrorState || !this._cells) {
|
||||
return;
|
||||
}
|
||||
let index = this._cells.findIndex((cell) => cell.equals(cellModel));
|
||||
if (index > -1) {
|
||||
this._cells.splice(index, 1);
|
||||
this._contentChangedEmitter.fire({
|
||||
changeType: NotebookChangeType.CellDeleted,
|
||||
cells: [cellModel],
|
||||
cellIndex: index
|
||||
});
|
||||
} else {
|
||||
this.notifyError(localize('deleteCellFailed', 'Failed to delete cell.'));
|
||||
}
|
||||
}
|
||||
|
||||
private notifyError(error: string): void {
|
||||
this.onErrorEmitter.fire(new ErrorInfo(error, MessageLevel.Error));
|
||||
}
|
||||
|
||||
public backgroundStartSession(): void {
|
||||
this._clientSession = this.notebookOptions.factory.createClientSession({
|
||||
path: this.notebookOptions.path,
|
||||
notebookManager: this.notebookManager,
|
||||
notificationService: this.notebookOptions.notificationService
|
||||
});
|
||||
let id: string = this.connectionProfile ? this.connectionProfile.id : undefined;
|
||||
|
||||
this._hadoopConnection = this.connectionProfile ? new NotebookConnection(this.connectionProfile) : undefined;
|
||||
this._clientSession.initialize(this._hadoopConnection);
|
||||
this._sessionLoadFinished = this._clientSession.ready.then(async () => {
|
||||
if (this._clientSession.isInErrorState) {
|
||||
this.setErrorState(this._clientSession.errorMessage);
|
||||
} else {
|
||||
this._onClientSessionReady.fire(this._clientSession);
|
||||
// Once session is loaded, can use the session manager to retrieve useful info
|
||||
this.loadKernelInfo();
|
||||
await this.loadActiveContexts(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get languageInfo(): nb.ILanguageInfo {
|
||||
return this._defaultLanguageInfo;
|
||||
}
|
||||
|
||||
private updateLanguageInfo(info: nb.ILanguageInfo) {
|
||||
if (info) {
|
||||
this._defaultLanguageInfo = info;
|
||||
}
|
||||
}
|
||||
|
||||
public changeKernel(displayName: string): void {
|
||||
let spec = this.getSpecNameFromDisplayName(displayName);
|
||||
this.doChangeKernel(spec);
|
||||
}
|
||||
|
||||
private doChangeKernel(kernelSpec: nb.IKernelSpec): void {
|
||||
this._clientSession.changeKernel(kernelSpec)
|
||||
.then((kernel) => {
|
||||
kernel.ready.then(() => {
|
||||
if (kernel.info) {
|
||||
this.updateLanguageInfo(kernel.info.language_info);
|
||||
}
|
||||
}, err => undefined);
|
||||
return this.updateKernelInfo(kernel);
|
||||
}).catch((err) => {
|
||||
this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err)));
|
||||
// TODO should revert kernels dropdown
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public changeContext(host: string): void {
|
||||
try {
|
||||
let newConnection: IConnectionProfile = this._activeContexts.otherConnections.find((connection) => connection.options['host'] === host);
|
||||
if (!newConnection && this._activeContexts.defaultConnection.options['host'] === host) {
|
||||
newConnection = this._activeContexts.defaultConnection;
|
||||
}
|
||||
if (newConnection) {
|
||||
SparkMagicContexts.configureContext(newConnection, this.notebookOptions);
|
||||
this._hadoopConnection = new NotebookConnection(newConnection);
|
||||
this._clientSession.updateConnection(this._hadoopConnection);
|
||||
}
|
||||
} catch (err) {
|
||||
let msg = notebookUtils.getErrorMessage(err);
|
||||
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
||||
}
|
||||
}
|
||||
|
||||
private loadKernelInfo(): void {
|
||||
this.clientSession.kernelChanged(async (e) => {
|
||||
await this.loadActiveContexts(e);
|
||||
});
|
||||
try {
|
||||
let sessionManager = this.notebookManager.sessionManager;
|
||||
if (sessionManager) {
|
||||
let defaultKernel = SparkMagicContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo, this.notebookOptions.notificationService);
|
||||
this._defaultKernel = defaultKernel;
|
||||
this._clientSession.statusChanged(async (session) => {
|
||||
if (session && session.defaultKernelLoaded === true) {
|
||||
this._kernelsChangedEmitter.fire(defaultKernel);
|
||||
} else if (session && !session.defaultKernelLoaded) {
|
||||
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
|
||||
}
|
||||
});
|
||||
this.doChangeKernel(defaultKernel);
|
||||
}
|
||||
} catch (err) {
|
||||
let msg = notebookUtils.getErrorMessage(err);
|
||||
this.notifyError(localize('loadKernelFailed', 'Loading kernel info failed: {0}', msg));
|
||||
}
|
||||
}
|
||||
|
||||
// Get default language if saved in notebook file
|
||||
// Otherwise, default to python
|
||||
private getDefaultLanguageInfo(notebook: nb.INotebook): nb.ILanguageInfo {
|
||||
return notebook!.metadata!.language_info || {
|
||||
name: 'python',
|
||||
version: '',
|
||||
mimetype: 'x-python'
|
||||
};
|
||||
}
|
||||
|
||||
// Get default kernel info if saved in notebook file
|
||||
private getSavedKernelInfo(notebook: nb.INotebook): nb.IKernelInfo {
|
||||
return notebook!.metadata!.kernelspec;
|
||||
}
|
||||
|
||||
private getSpecNameFromDisplayName(displayName: string): nb.IKernelSpec {
|
||||
displayName = this.sanitizeDisplayName(displayName);
|
||||
let kernel: nb.IKernelSpec = this.specs.kernels.find(k => k.display_name.toLowerCase() === displayName.toLowerCase());
|
||||
if (!kernel) {
|
||||
return undefined; // undefined is handled gracefully in the session to default to the default kernel
|
||||
} else if (!kernel.name) {
|
||||
kernel.name = this.specs.defaultKernel;
|
||||
}
|
||||
return kernel;
|
||||
}
|
||||
|
||||
private setErrorState(errMsg: string): void {
|
||||
this._inErrorState = true;
|
||||
let msg = localize('startSessionFailed', 'Could not start session: {0}', errMsg);
|
||||
this.notifyError(msg);
|
||||
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.handleClosed();
|
||||
}
|
||||
|
||||
public async handleClosed(): Promise<void> {
|
||||
try {
|
||||
if (this._clientSession) {
|
||||
await this._clientSession.shutdown();
|
||||
this._clientSession = undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
this.notifyError(localize('shutdownError', 'An error occurred when closing the notebook: {0}', err));
|
||||
}
|
||||
}
|
||||
|
||||
private async loadActiveContexts(kernelChangedArgs: nb.IKernelChangedArgs): Promise<void> {
|
||||
this._activeContexts = await SparkMagicContexts.getContextsForKernel(this.notebookOptions.connectionService, kernelChangedArgs, this.connectionProfile);
|
||||
this._contextsChangedEmitter.fire();
|
||||
let defaultHadoopConnection = new NotebookConnection(this.contexts.defaultConnection);
|
||||
this.changeContext(defaultHadoopConnection.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes display name to remove IP address in order to fairly compare kernels
|
||||
* In some notebooks, display name is in the format <kernel> (<ip address>)
|
||||
* example: PySpark (25.23.32.4)
|
||||
* @param displayName Display Name for the kernel
|
||||
*/
|
||||
public sanitizeDisplayName(displayName: string): string {
|
||||
let name = displayName;
|
||||
if (name) {
|
||||
let index = name.indexOf('(');
|
||||
name = (index > -1) ? name.substr(0, index - 1).trim() : name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public async saveModel(): Promise<boolean> {
|
||||
let notebook = this.toJSON();
|
||||
if (!notebook) {
|
||||
return false;
|
||||
}
|
||||
await this.notebookManager.contentManager.save(this.notebookOptions.path, notebook);
|
||||
this._contentChangedEmitter.fire({
|
||||
changeType: NotebookChangeType.DirtyStateChanged,
|
||||
isDirty: false
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private async updateKernelInfo(kernel: nb.IKernel): Promise<void> {
|
||||
if (kernel) {
|
||||
try {
|
||||
let spec = await kernel.getSpec();
|
||||
this._savedKernelInfo = {
|
||||
name: kernel.name,
|
||||
display_name: spec.display_name,
|
||||
language: spec.language
|
||||
};
|
||||
} catch (err) {
|
||||
// Don't worry about this for now. Just use saved values
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Serialize the model to JSON.
|
||||
*/
|
||||
toJSON(): nb.INotebook {
|
||||
let cells: nb.ICell[] = this.cells.map(c => c.toJSON());
|
||||
let metadata = Object.create(null) as nb.INotebookMetadata;
|
||||
// TODO update language and kernel when these change
|
||||
metadata.kernelspec = this._savedKernelInfo;
|
||||
metadata.language_info = this.languageInfo;
|
||||
return {
|
||||
metadata,
|
||||
nbformat_minor: this._nbformatMinor,
|
||||
nbformat: this._nbformat,
|
||||
cells
|
||||
};
|
||||
}
|
||||
|
||||
onCellChange(cell: CellModel, change: NotebookChangeType): void {
|
||||
let changeInfo: NotebookContentChange = {
|
||||
changeType: change,
|
||||
cells: [cell]
|
||||
};
|
||||
switch (change) {
|
||||
case NotebookChangeType.CellOutputUpdated:
|
||||
case NotebookChangeType.CellSourceUpdated:
|
||||
changeInfo.changeType = NotebookChangeType.DirtyStateChanged;
|
||||
changeInfo.isDirty = true;
|
||||
break;
|
||||
default:
|
||||
// Do nothing for now
|
||||
}
|
||||
this._contentChangedEmitter.fire(changeInfo);
|
||||
}
|
||||
|
||||
}
|
||||
194
src/sql/parts/notebook/models/sparkMagicContexts.ts
Normal file
194
src/sql/parts/notebook/models/sparkMagicContexts.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { IDefaultConnection, notebookConstants, INotebookModelOptions } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
|
||||
export class SparkMagicContexts {
|
||||
|
||||
public static get DefaultContext(): IDefaultConnection {
|
||||
// TODO NOTEBOOK REFACTOR fix default connection handling
|
||||
let defaultConnection: IConnectionProfile = <any> {
|
||||
providerName: notebookConstants.hadoopKnoxProviderName,
|
||||
id: '-1',
|
||||
options:
|
||||
{
|
||||
host: localize('selectConnection', 'Select Connection')
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// default context if no other contexts are applicable
|
||||
defaultConnection: defaultConnection,
|
||||
otherConnections: [defaultConnection]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the applicable contexts for a given kernel
|
||||
* @param apiWrapper ApiWrapper
|
||||
* @param kernelChangedArgs kernel changed args (both old and new kernel info)
|
||||
* @param profile current connection profile
|
||||
*/
|
||||
public static async getContextsForKernel(connectionService: IConnectionManagementService, kernelChangedArgs?: nb.IKernelChangedArgs, profile?: IConnectionProfile): Promise<IDefaultConnection> {
|
||||
let connections: IDefaultConnection = this.DefaultContext;
|
||||
if (!profile) {
|
||||
if (!kernelChangedArgs || !kernelChangedArgs.newValue ||
|
||||
(kernelChangedArgs.oldValue && kernelChangedArgs.newValue.id === kernelChangedArgs.oldValue.id)) {
|
||||
// nothing to do, kernels are the same or new kernel is undefined
|
||||
return connections;
|
||||
}
|
||||
}
|
||||
if (kernelChangedArgs && kernelChangedArgs.newValue && kernelChangedArgs.newValue.name) {
|
||||
switch (kernelChangedArgs.newValue.name) {
|
||||
case (notebookConstants.python3):
|
||||
// python3 case, use this.DefaultContext for the only connection
|
||||
break;
|
||||
//TO DO: Handle server connections based on kernel type. Right now, we call the same method for all kernel types.
|
||||
default:
|
||||
connections = await this.getActiveContexts(connectionService, profile);
|
||||
}
|
||||
} else {
|
||||
connections = await this.getActiveContexts(connectionService, profile);
|
||||
}
|
||||
return connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active contexts and sort them
|
||||
* @param apiWrapper ApiWrapper
|
||||
* @param profile current connection profile
|
||||
*/
|
||||
public static async getActiveContexts(connectionService: IConnectionManagementService, profile: IConnectionProfile): Promise<IDefaultConnection> {
|
||||
let defaultConnection: IConnectionProfile = SparkMagicContexts.DefaultContext.defaultConnection;
|
||||
let activeConnections: IConnectionProfile[] = await connectionService.getActiveConnections();
|
||||
// If no connections exist, only show 'n/a'
|
||||
if (activeConnections.length === 0) {
|
||||
return SparkMagicContexts.DefaultContext;
|
||||
}
|
||||
activeConnections = activeConnections.filter(conn => conn.providerName === notebookConstants.hadoopKnoxProviderName);
|
||||
|
||||
// If launched from the right click or server dashboard, connection profile data exists, so use that as default
|
||||
if (profile && profile.options) {
|
||||
let profileConnection = activeConnections.filter(conn => conn.options['host'] === profile.options['host']);
|
||||
if (profileConnection) {
|
||||
defaultConnection = profileConnection[0];
|
||||
}
|
||||
} else {
|
||||
if (activeConnections.length > 0) {
|
||||
defaultConnection = activeConnections[0];
|
||||
} else {
|
||||
// TODO NOTEBOOK REFACTOR change this so it's no longer incompatible with IConnectionProfile
|
||||
defaultConnection = <IConnectionProfile> <any>{
|
||||
providerName: notebookConstants.hadoopKnoxProviderName,
|
||||
id: '-1',
|
||||
options:
|
||||
{
|
||||
host: localize('addConnection', 'Add new connection')
|
||||
}
|
||||
};
|
||||
activeConnections.push(defaultConnection);
|
||||
}
|
||||
}
|
||||
return {
|
||||
otherConnections: activeConnections,
|
||||
defaultConnection: defaultConnection
|
||||
};
|
||||
}
|
||||
|
||||
public static async configureContext(connection: IConnectionProfile, options: INotebookModelOptions): Promise<object> {
|
||||
let sparkmagicConfDir = path.join(notebookUtils.getUserHome(), '.sparkmagic');
|
||||
// TODO NOTEBOOK REFACTOR re-enable this or move to extension. Requires config files to be available in order to work
|
||||
// await notebookUtils.mkDir(sparkmagicConfDir);
|
||||
|
||||
// let hadoopConnection = new Connection({ options: connection.options }, undefined, connection.connectionId);
|
||||
// await hadoopConnection.getCredential();
|
||||
// // Default to localhost in config file.
|
||||
// let creds: ICredentials = {
|
||||
// 'url': 'http://localhost:8088'
|
||||
// };
|
||||
|
||||
// let configPath = notebookUtils.getTemplatePath(options.extensionContext.extensionPath, path.join('jupyter_config', 'sparkmagic_config.json'));
|
||||
// let fileBuffer: Buffer = await pfs.readFile(configPath);
|
||||
// let fileContents: string = fileBuffer.toString();
|
||||
// let config: ISparkMagicConfig = json.parse(fileContents);
|
||||
// SparkMagicContexts.updateConfig(config, creds, sparkmagicConfDir);
|
||||
|
||||
// let configFilePath = path.join(sparkmagicConfDir, 'config.json');
|
||||
// await pfs.writeFile(configFilePath, JSON.stringify(config));
|
||||
|
||||
return {'SPARKMAGIC_CONF_DIR': sparkmagicConfDir};
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param specs kernel specs (comes from session manager)
|
||||
* @param connectionInfo connection profile
|
||||
* @param savedKernelInfo kernel info loaded from
|
||||
*/
|
||||
public static getDefaultKernel(specs: nb.IAllKernels, connectionInfo: IConnectionProfile, savedKernelInfo: nb.IKernelInfo, notificationService: INotificationService): nb.IKernelSpec {
|
||||
let defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
|
||||
let profile = connectionInfo as IConnectionProfile;
|
||||
if (specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
|
||||
// set default kernel to default spark kernel if profile exists
|
||||
// otherwise, set default to kernel info loaded from existing file
|
||||
defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo;
|
||||
} else {
|
||||
// Handle kernels
|
||||
if (savedKernelInfo && savedKernelInfo.name.toLowerCase().indexOf('spark') > -1) {
|
||||
notificationService.warn(localize('sparkKernelRequiresConnection', 'Cannot use kernel {0} as no connection is active. The default kernel of Python3 will be used instead.', savedKernelInfo.display_name));
|
||||
}
|
||||
}
|
||||
|
||||
// If no default kernel specified (should never happen), default to python3
|
||||
if (!defaultKernel) {
|
||||
defaultKernel = {
|
||||
name: notebookConstants.python3,
|
||||
display_name: notebookConstants.python3DisplayName
|
||||
};
|
||||
}
|
||||
return defaultKernel;
|
||||
}
|
||||
|
||||
private static updateConfig(config: ISparkMagicConfig, creds: ICredentials, homePath: string): void {
|
||||
config.kernel_python_credentials = creds;
|
||||
config.kernel_scala_credentials = creds;
|
||||
config.kernel_r_credentials = creds;
|
||||
config.logging_config.handlers.magicsHandler.home_path = homePath;
|
||||
}
|
||||
}
|
||||
|
||||
interface ICredentials {
|
||||
'url': string;
|
||||
}
|
||||
|
||||
interface ISparkMagicConfig {
|
||||
kernel_python_credentials: ICredentials;
|
||||
kernel_scala_credentials: ICredentials;
|
||||
kernel_r_credentials: ICredentials;
|
||||
logging_config: {
|
||||
handlers: {
|
||||
magicsHandler: {
|
||||
home_path: string;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export interface IKernelJupyterID {
|
||||
id: string;
|
||||
jupyterId: string;
|
||||
}
|
||||
@@ -5,17 +5,44 @@
|
||||
|
||||
import './notebookStyles';
|
||||
|
||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { ICellModel, CellTypes } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { INotebookService, INotebookParams } from 'sql/services/notebook/notebookService';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
|
||||
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
||||
|
||||
class CellModelStub implements ICellModel {
|
||||
public cellUri: URI;
|
||||
constructor(public id: string,
|
||||
public language: string,
|
||||
public source: string,
|
||||
public cellType: CellType,
|
||||
public trustedMode: boolean = false,
|
||||
public active: boolean = false
|
||||
) { }
|
||||
|
||||
equals(cellModel: ICellModel): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
toJSON(): nb.ICell {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: NOTEBOOK_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./notebook.component.html'))
|
||||
@@ -27,20 +54,18 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
|
||||
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
|
||||
@Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService,
|
||||
@Inject(INotificationService) private notificationService: INotificationService,
|
||||
@Inject(INotebookService) private notebookService: INotebookService,
|
||||
@Inject(IBootstrapParams) private notebookParams: INotebookParams
|
||||
) {
|
||||
super();
|
||||
|
||||
// Todo: This is mock data for cells. Will remove this code when we have a service
|
||||
let cell1: ICellModel = {
|
||||
id: '1', language: 'sql', source: 'select * from sys.tables', cellType: CellTypes.Code, active: false
|
||||
};
|
||||
let cell2: ICellModel = {
|
||||
id: '2', language: 'sql', source: 'select 1', cellType: CellTypes.Code, active: false
|
||||
};
|
||||
let cell3: ICellModel = {
|
||||
id: '3', language: 'markdown', source: '## This is test!', cellType: CellTypes.Markdown, active: false
|
||||
};
|
||||
// TODO NOTEBOOK REFACTOR: This is mock data for cells. Will remove this code when we have a service
|
||||
let cell1 : ICellModel = new CellModelStub ('1', 'sql', 'select * from sys.tables', CellTypes.Code);
|
||||
let cell2 : ICellModel = new CellModelStub ('2', 'sql', 'select 1', CellTypes.Code);
|
||||
let cell3 : ICellModel = new CellModelStub ('3', 'markdown', '## This is test!', CellTypes.Markdown);
|
||||
this.cells.push(cell1, cell2, cell3);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,14 @@ import { Action } from 'vs/base/common/actions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
import { NotebookInput, NotebookInputModel } from 'sql/parts/notebook/notebookInput';
|
||||
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
|
||||
let counter = 0;
|
||||
|
||||
/**
|
||||
* todo: Will remove this code.
|
||||
@@ -34,7 +39,8 @@ export class OpenNotebookAction extends Action {
|
||||
|
||||
public run(): TPromise<void> {
|
||||
return new TPromise<void>((resolve, reject) => {
|
||||
let model = new NotebookInputModel('modelViewId', undefined, undefined);
|
||||
let untitledUri = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter++}`});
|
||||
let model = new NotebookInputModel(untitledUri, undefined, undefined);
|
||||
let input = new NotebookInput('modelViewId', model);
|
||||
this._editorService.openEditor(input, { pinned: true });
|
||||
});
|
||||
|
||||
19
src/sql/parts/notebook/notebookConstants.ts
Normal file
19
src/sql/parts/notebook/notebookConstants.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export namespace nbversion {
|
||||
/**
|
||||
* The major version of the notebook format.
|
||||
*/
|
||||
export const MAJOR_VERSION: number = 4;
|
||||
|
||||
/**
|
||||
* The minor version of the notebook format.
|
||||
*/
|
||||
export const MINOR_VERSION: number = 2;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||
import { NotebookModule } from 'sql/parts/notebook/notebook.module';
|
||||
import { NOTEBOOK_SELECTOR } from 'sql/parts/notebook/notebook.component';
|
||||
import { INotebookParams, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||
|
||||
export class NotebookEditor extends BaseEditor {
|
||||
|
||||
@@ -83,12 +84,16 @@ export class NotebookEditor extends BaseEditor {
|
||||
private bootstrapAngular(input: NotebookInput): void {
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
input.hasBootstrapped = true;
|
||||
let params: INotebookParams = {
|
||||
notebookUri: input.notebookUri,
|
||||
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER
|
||||
};
|
||||
bootstrapAngular(this.instantiationService,
|
||||
NotebookModule,
|
||||
this._notebookContainer,
|
||||
NOTEBOOK_SELECTOR,
|
||||
undefined,
|
||||
undefined
|
||||
params,
|
||||
input
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
||||
|
||||
export class NotebookInputModel extends EditorModel {
|
||||
private dirty: boolean;
|
||||
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onDidChangeDirty(): Event<void> { return this._onDidChangeDirty.event; }
|
||||
|
||||
constructor(public readonly modelViewId, private readonly handle: number, private saveHandler?: ModeViewSaveHandler) {
|
||||
private _providerId: string;
|
||||
constructor(public readonly notebookUri: URI, private readonly handle: number, private saveHandler?: ModeViewSaveHandler) {
|
||||
super();
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return this._providerId;
|
||||
}
|
||||
|
||||
public set providerId(value: string) {
|
||||
this._providerId = value;
|
||||
}
|
||||
|
||||
get onDidChangeDirty(): Event<void> {
|
||||
return this._onDidChangeDirty.event;
|
||||
}
|
||||
|
||||
get isDirty(): boolean {
|
||||
return this.dirty;
|
||||
}
|
||||
@@ -55,8 +73,12 @@ export class NotebookInput extends EditorInput {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
public get modelViewId(): string {
|
||||
return this._model.modelViewId;
|
||||
public get notebookUri(): URI {
|
||||
return this._model.notebookUri;
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return this._model.providerId;
|
||||
}
|
||||
|
||||
public getTypeId(): string {
|
||||
|
||||
38
src/sql/parts/notebook/notebookUtils.ts
Normal file
38
src/sql/parts/notebook/notebookUtils.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
import * as os from 'os';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
|
||||
|
||||
|
||||
/**
|
||||
* Test whether an output is from a stream.
|
||||
*/
|
||||
export function isStream(output: nb.ICellOutput): output is nb.IStreamResult {
|
||||
return output.output_type === 'stream';
|
||||
}
|
||||
|
||||
export function getErrorMessage(error: Error | string): string {
|
||||
return (error instanceof Error) ? error.message : error;
|
||||
}
|
||||
|
||||
export function getUserHome(): string {
|
||||
return process.env.HOME || process.env.USERPROFILE;
|
||||
}
|
||||
|
||||
export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Promise<void> {
|
||||
let exists = await pfs.dirExists(dirPath);
|
||||
if (!exists) {
|
||||
if (outputChannel) {
|
||||
outputChannel.append(localize('mkdirOutputMsg', '... Creating {0}', dirPath) + os.EOL);
|
||||
}
|
||||
await pfs.mkdirp(dirPath);
|
||||
}
|
||||
}
|
||||
16
src/sql/parts/notebook/spark/sparkUtils.ts
Normal file
16
src/sql/parts/notebook/spark/sparkUtils.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: The content of this file should be refactored to an extension
|
||||
export function getKnoxUrl(host: string, port: string): string {
|
||||
return `https://${host}:${port}/gateway`;
|
||||
}
|
||||
|
||||
export function getLivyUrl(serverName: string, port: string): string {
|
||||
return getKnoxUrl(serverName, port) + '/default/livy/v1/';
|
||||
}
|
||||
59
src/sql/services/notebook/notebookService.ts
Normal file
59
src/sql/services/notebook/notebookService.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
|
||||
export const SERVICE_ID = 'notebookService';
|
||||
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
||||
|
||||
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
||||
|
||||
export interface INotebookService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: INotebookProvider): void;
|
||||
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
unregisterProvider(providerId: string): void;
|
||||
|
||||
/**
|
||||
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
|
||||
* run cells in a notebook.
|
||||
* @param providerId ID for the provider to be used to instantiate a backend notebook service
|
||||
* @param uri URI for a notebook that is to be opened. Based on this an existing manager may be used, or
|
||||
* a new one may need to be created
|
||||
*/
|
||||
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
|
||||
|
||||
shutdown(): void;
|
||||
}
|
||||
|
||||
export interface INotebookProvider {
|
||||
readonly providerId: string;
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager>;
|
||||
handleNotebookClosed(notebookUri: URI): void;
|
||||
}
|
||||
|
||||
export interface INotebookManager {
|
||||
providerId: string;
|
||||
readonly contentManager: sqlops.nb.ContentManager;
|
||||
readonly sessionManager: sqlops.nb.SessionManager;
|
||||
readonly serverManager: sqlops.nb.ServerManager;
|
||||
}
|
||||
|
||||
export interface INotebookParams extends IBootstrapParams {
|
||||
notebookUri: URI;
|
||||
providerId: string;
|
||||
}
|
||||
60
src/sql/services/notebook/notebookServiceImpl.ts
Normal file
60
src/sql/services/notebook/notebookServiceImpl.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
import * as nls from 'vs/nls';
|
||||
import { INotebookService, INotebookManager, INotebookProvider } from 'sql/services/notebook/notebookService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export class NotebookService implements INotebookService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _providers: Map<string, INotebookProvider> = new Map();
|
||||
private _managers: Map<URI, INotebookManager> = new Map();
|
||||
|
||||
registerProvider(providerId: string, provider: INotebookProvider): void {
|
||||
this._providers.set(providerId, provider);
|
||||
}
|
||||
|
||||
unregisterProvider(providerId: string): void {
|
||||
this._providers.delete(providerId);
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this._managers.forEach(manager => {
|
||||
if (manager.serverManager) {
|
||||
// TODO should this thenable be awaited?
|
||||
manager.serverManager.stopServer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getOrCreateNotebookManager(providerId: string, uri: URI): Promise<INotebookManager> {
|
||||
if (!uri) {
|
||||
throw new Error(nls.localize('notebookUriNotDefined', 'No URI was passed when creating a notebook manager'));
|
||||
}
|
||||
let manager = this._managers.get(uri);
|
||||
if (!manager) {
|
||||
manager = await this.doWithProvider(providerId, (provider) => provider.getNotebookManager(uri));
|
||||
if (manager) {
|
||||
this._managers.set(uri, manager);
|
||||
}
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
private doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Thenable<T> {
|
||||
// Make sure the provider exists before attempting to retrieve accounts
|
||||
let provider = this._providers.get(providerId);
|
||||
if (!provider) {
|
||||
return Promise.reject(new Error(nls.localize('notebookServiceNoProvider', 'Notebook provider does not exist'))).then();
|
||||
}
|
||||
|
||||
return op(provider);
|
||||
}
|
||||
}
|
||||
620
src/sql/sqlops.proposed.d.ts
vendored
620
src/sql/sqlops.proposed.d.ts
vendored
@@ -52,8 +52,8 @@ declare module 'sqlops' {
|
||||
}
|
||||
|
||||
export interface TreeComponentView<T> extends vscode.Disposable {
|
||||
onNodeCheckedChanged: vscode.Event<NodeCheckedEventParameters<T>>;
|
||||
onDidChangeSelection: vscode.Event<vscode.TreeViewSelectionChangeEvent<T>>;
|
||||
onNodeCheckedChanged: vscode.Event<NodeCheckedEventParameters<T>>;
|
||||
onDidChangeSelection: vscode.Event<vscode.TreeViewSelectionChangeEvent<T>>;
|
||||
}
|
||||
|
||||
export class TreeComponentItem extends vscode.TreeItem {
|
||||
@@ -1365,4 +1365,620 @@ declare module 'sqlops' {
|
||||
*/
|
||||
export function openConnectionDialog(providers?: string[], initialConnectionProfile?: IConnectionProfile, connectionCompletionOptions?: IConnectionCompletionOptions): Thenable<connection.Connection>;
|
||||
}
|
||||
|
||||
export namespace nb {
|
||||
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
|
||||
|
||||
export interface NotebookProvider {
|
||||
handle: number;
|
||||
readonly providerId: string;
|
||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<NotebookManager>;
|
||||
handleNotebookClosed(notebookUri: vscode.Uri): void;
|
||||
}
|
||||
|
||||
export interface NotebookManager {
|
||||
/**
|
||||
* Manages reading and writing contents to/from files.
|
||||
* Files may be local or remote, with this manager giving them a chance to convert and migrate
|
||||
* from specific notebook file types to and from a standard type for this UI
|
||||
*/
|
||||
readonly contentManager: ContentManager;
|
||||
/**
|
||||
* A SessionManager that handles starting, stopping and handling notifications around sessions.
|
||||
* Each notebook has 1 session associated with it, and the session is responsible
|
||||
* for kernel management
|
||||
*/
|
||||
readonly sessionManager: SessionManager;
|
||||
/**
|
||||
* (Optional) ServerManager to handle server lifetime management operations.
|
||||
* Depending on the implementation this may not be needed.
|
||||
*/
|
||||
readonly serverManager?: ServerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the contracts needed to manage the lifetime of a notebook server.
|
||||
*/
|
||||
export interface ServerManager {
|
||||
/**
|
||||
* Indicates if the server is started at the current time
|
||||
*/
|
||||
readonly isStarted: boolean;
|
||||
|
||||
/**
|
||||
* Event sent when the server has started. This can be used to query
|
||||
* the manager for server settings
|
||||
*/
|
||||
readonly onServerStarted: vscode.Event<void>;
|
||||
|
||||
/**
|
||||
* Starts the server. Some server types may not support or require this.
|
||||
* Should no-op if server is already started
|
||||
*/
|
||||
startServer(): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Stops the server. Some server types may not support or require this
|
||||
*/
|
||||
stopServer(): Thenable<void>;
|
||||
}
|
||||
|
||||
//#region Content APIs
|
||||
/**
|
||||
* Handles interacting with file and folder contents
|
||||
*/
|
||||
export interface ContentManager {
|
||||
/* Reads contents from a Uri representing a local or remote notebook and returns a
|
||||
* JSON object containing the cells and metadata about the notebook
|
||||
*/
|
||||
getNotebookContents(path: string): Thenable<INotebook>;
|
||||
|
||||
/**
|
||||
* Save a file.
|
||||
*
|
||||
* @param path - The desired file path.
|
||||
*
|
||||
* @param notebook - notebook to be saved.
|
||||
*
|
||||
* @returns A thenable which resolves with the file content model when the
|
||||
* file is saved.
|
||||
*/
|
||||
save(path: string, notebook: INotebook): Thenable<INotebook>;
|
||||
}
|
||||
|
||||
export interface INotebook {
|
||||
|
||||
readonly cells: ICell[];
|
||||
readonly metadata: INotebookMetadata;
|
||||
readonly nbformat: number;
|
||||
readonly nbformat_minor: number;
|
||||
}
|
||||
|
||||
export interface INotebookMetadata {
|
||||
kernelspec: IKernelInfo;
|
||||
language_info?: ILanguageInfo;
|
||||
}
|
||||
|
||||
export interface IKernelInfo {
|
||||
name: string;
|
||||
language?: string;
|
||||
display_name?: string;
|
||||
}
|
||||
|
||||
export interface ILanguageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
mimetype?: string;
|
||||
codemirror_mode?: string | ICodeMirrorMode;
|
||||
}
|
||||
|
||||
export interface ICodeMirrorMode {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface ICell {
|
||||
cell_type: CellType;
|
||||
source: string | string[];
|
||||
metadata: {
|
||||
language?: string;
|
||||
};
|
||||
execution_count: number;
|
||||
outputs?: ICellOutput[];
|
||||
}
|
||||
|
||||
export type CellType = 'code' | 'markdown' | 'raw';
|
||||
|
||||
export interface ICellOutput {
|
||||
output_type: OutputType;
|
||||
}
|
||||
export interface IStreamResult extends ICellOutput {
|
||||
/**
|
||||
* Stream output field defining the stream name, for example stdout
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Stream output field defining the multiline stream text
|
||||
*/
|
||||
text: string | Buffer;
|
||||
}
|
||||
export interface IDisplayResult extends ICellOutput {
|
||||
/**
|
||||
* Mime bundle expected to contain mime type -> contents mappings.
|
||||
* This is dynamic and is controlled by kernels, so cannot be more specific
|
||||
*/
|
||||
data: {};
|
||||
/**
|
||||
* Optional metadata, also a mime bundle
|
||||
*/
|
||||
metadata?: {};
|
||||
}
|
||||
export interface IExecuteResult extends IDisplayResult {
|
||||
/**
|
||||
* Number of times the cell was executed
|
||||
*/
|
||||
executionCount: number;
|
||||
}
|
||||
export interface IErrorResult extends ICellOutput {
|
||||
/**
|
||||
* Exception name
|
||||
*/
|
||||
ename: string;
|
||||
/**
|
||||
* Exception value
|
||||
*/
|
||||
evalue: string;
|
||||
/**
|
||||
* Stacktrace equivalent
|
||||
*/
|
||||
traceback?: string[];
|
||||
}
|
||||
|
||||
export type OutputType =
|
||||
| 'execute_result'
|
||||
| 'display_data'
|
||||
| 'stream'
|
||||
| 'error'
|
||||
| 'update_display_data';
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Session APIs
|
||||
export interface SessionManager {
|
||||
/**
|
||||
* Indicates whether the manager is ready.
|
||||
*/
|
||||
readonly isReady: boolean;
|
||||
|
||||
/**
|
||||
* A Thenable that is fulfilled when the manager is ready.
|
||||
*/
|
||||
readonly ready: Thenable<void>;
|
||||
|
||||
readonly specs: IAllKernels | undefined;
|
||||
|
||||
startNew(options: ISessionOptions): Thenable<ISession>;
|
||||
|
||||
shutdown(id: string): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface ISession {
|
||||
/**
|
||||
* Is change of kernels supported for this session?
|
||||
*/
|
||||
canChangeKernels: boolean;
|
||||
/*
|
||||
* Unique id of the session.
|
||||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* The current path associated with the session.
|
||||
*/
|
||||
readonly path: string;
|
||||
|
||||
/**
|
||||
* The current name associated with the session.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* The type of the session.
|
||||
*/
|
||||
readonly type: string;
|
||||
|
||||
/**
|
||||
* The status indicates if the kernel is healthy, dead, starting, etc.
|
||||
*/
|
||||
readonly status: KernelStatus;
|
||||
|
||||
/**
|
||||
* The kernel.
|
||||
*
|
||||
* #### Notes
|
||||
* This is a read-only property, and can be altered by [changeKernel].
|
||||
*/
|
||||
readonly kernel: IKernel;
|
||||
|
||||
/**
|
||||
* Tracks whether the default kernel failed to load
|
||||
* This could be for a reason such as the kernel name not being recognized as a valid kernel;
|
||||
*/
|
||||
defaultKernelLoaded?: boolean;
|
||||
|
||||
changeKernel(kernelInfo: IKernelSpec): Thenable<IKernel>;
|
||||
}
|
||||
|
||||
export interface ISessionOptions {
|
||||
/**
|
||||
* The path (not including name) to the session.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* The name of the session.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* The type of the session.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* The type of kernel (e.g. python3).
|
||||
*/
|
||||
kernelName?: string;
|
||||
/**
|
||||
* The id of an existing kernel.
|
||||
*/
|
||||
kernelId?: string;
|
||||
}
|
||||
|
||||
export interface IKernel {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly supportsIntellisense: boolean;
|
||||
/**
|
||||
* Test whether the kernel is ready.
|
||||
*/
|
||||
readonly isReady: boolean;
|
||||
|
||||
/**
|
||||
* A Thenable that is fulfilled when the kernel is ready.
|
||||
*/
|
||||
readonly ready: Thenable<void>;
|
||||
|
||||
/**
|
||||
* The cached kernel info.
|
||||
*
|
||||
* #### Notes
|
||||
* This value will be null until the kernel is ready.
|
||||
*/
|
||||
readonly info: IInfoReply | null;
|
||||
|
||||
/**
|
||||
* Gets the full specification for this kernel, which can be serialized to
|
||||
* a noteobok file
|
||||
*/
|
||||
getSpec(): Thenable<IKernelSpec>;
|
||||
|
||||
/**
|
||||
* Send an `execute_request` message.
|
||||
*
|
||||
* @param content - The content of the request.
|
||||
*
|
||||
* @param disposeOnDone - Whether to dispose of the future when done.
|
||||
*
|
||||
* @returns A kernel future.
|
||||
*
|
||||
* #### Notes
|
||||
* See [Messaging in
|
||||
* Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute).
|
||||
*
|
||||
* This method returns a kernel future, rather than a Thenable, since execution may
|
||||
* have many response messages (for example, many iopub display messages).
|
||||
*
|
||||
* Future `onReply` is called with the `execute_reply` content when the
|
||||
* shell reply is received and validated.
|
||||
*
|
||||
* **See also:** [[IExecuteReply]]
|
||||
*/
|
||||
requestExecute(content: IExecuteRequest, disposeOnDone?: boolean): IFuture;
|
||||
|
||||
|
||||
/**
|
||||
* Send a `complete_request` message.
|
||||
*
|
||||
* @param content - The content of the request.
|
||||
*
|
||||
* @returns A Thenable that resolves with the response message.
|
||||
*
|
||||
* #### Notes
|
||||
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion).
|
||||
*
|
||||
* Fulfills with the `complete_reply` content when the shell reply is
|
||||
* received and validated.
|
||||
*/
|
||||
requestComplete(content: ICompleteRequest): Thenable<ICompleteReplyMsg>;
|
||||
|
||||
}
|
||||
|
||||
export interface IInfoReply {
|
||||
protocol_version: string;
|
||||
implementation: string;
|
||||
implementation_version: string;
|
||||
language_info: ILanguageInfo;
|
||||
banner: string;
|
||||
help_links: {
|
||||
text: string;
|
||||
url: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The contents of a requestExecute message sent to the server.
|
||||
*/
|
||||
export interface IExecuteRequest extends IExecuteOptions {
|
||||
code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options used to configure an execute request.
|
||||
*/
|
||||
export interface IExecuteOptions {
|
||||
/**
|
||||
* Whether to execute the code as quietly as possible.
|
||||
* The default is `false`.
|
||||
*/
|
||||
silent?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to store history of the execution.
|
||||
* The default `true` if silent is False.
|
||||
* It is forced to `false ` if silent is `true`.
|
||||
*/
|
||||
store_history?: boolean;
|
||||
|
||||
/**
|
||||
* A mapping of names to expressions to be evaluated in the
|
||||
* kernel's interactive namespace.
|
||||
*/
|
||||
user_expressions?: {};
|
||||
|
||||
/**
|
||||
* Whether to allow stdin requests.
|
||||
* The default is `true`.
|
||||
*/
|
||||
allow_stdin?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to the abort execution queue on an error.
|
||||
* The default is `false`.
|
||||
*/
|
||||
stop_on_error?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content of a `'complete_request'` message.
|
||||
*
|
||||
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion).
|
||||
*
|
||||
* **See also:** [[ICompleteReply]], [[IKernel.complete]]
|
||||
*/
|
||||
export interface ICompleteRequest {
|
||||
code: string;
|
||||
cursor_pos: number;
|
||||
}
|
||||
|
||||
export interface ICompletionContent {
|
||||
matches: string[];
|
||||
cursor_start: number;
|
||||
cursor_end: number;
|
||||
metadata: any;
|
||||
status: 'ok' | 'error';
|
||||
}
|
||||
/**
|
||||
* A `'complete_reply'` message on the `'stream'` channel.
|
||||
*
|
||||
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion).
|
||||
*
|
||||
* **See also:** [[ICompleteRequest]], [[IKernel.complete]]
|
||||
*/
|
||||
export interface ICompleteReplyMsg extends IShellMessage {
|
||||
content: ICompletionContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The valid Kernel status states.
|
||||
*/
|
||||
export type KernelStatus =
|
||||
| 'unknown'
|
||||
| 'starting'
|
||||
| 'reconnecting'
|
||||
| 'idle'
|
||||
| 'busy'
|
||||
| 'restarting'
|
||||
| 'dead'
|
||||
| 'connected';
|
||||
|
||||
/**
|
||||
* An arguments object for the kernel changed event.
|
||||
*/
|
||||
export interface IKernelChangedArgs {
|
||||
oldValue: IKernel | null;
|
||||
newValue: IKernel | null;
|
||||
}
|
||||
|
||||
/// -------- JSON objects, and objects primarily intended not to have methods -----------
|
||||
export interface IAllKernels {
|
||||
defaultKernel: string;
|
||||
kernels: IKernelSpec[];
|
||||
}
|
||||
export interface IKernelSpec {
|
||||
name: string;
|
||||
language?: string;
|
||||
display_name?: string;
|
||||
}
|
||||
|
||||
export interface MessageHandler<T extends IMessage> {
|
||||
handle(message: T): void | Thenable<void>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A Future interface for responses from the kernel.
|
||||
*
|
||||
* When a message is sent to a kernel, a Future is created to handle any
|
||||
* responses that may come from the kernel.
|
||||
*/
|
||||
export interface IFuture extends vscode.Disposable {
|
||||
|
||||
/**
|
||||
* The original outgoing message.
|
||||
*/
|
||||
readonly msg: IMessage;
|
||||
|
||||
/**
|
||||
* A Thenable that resolves when the future is done.
|
||||
*
|
||||
* #### Notes
|
||||
* The future is done when there are no more responses expected from the
|
||||
* kernel.
|
||||
*
|
||||
* The `done` Thenable resolves to the reply message if there is one,
|
||||
* otherwise it resolves to `undefined`.
|
||||
*/
|
||||
readonly done: Thenable<IShellMessage | undefined>;
|
||||
|
||||
/**
|
||||
* Set the reply handler for the kernel future.
|
||||
*
|
||||
* #### Notes
|
||||
* If the handler returns a Thenable, all kernel message processing pauses
|
||||
* until the Thenable is resolved. If there is a reply message, the future
|
||||
* `done` Thenable also resolves to the reply message after this handler has
|
||||
* been called.
|
||||
*/
|
||||
setReplyHandler(handler: MessageHandler<IShellMessage>): void;
|
||||
|
||||
/**
|
||||
* Sets the stdin handler for the kernel future.
|
||||
*
|
||||
* #### Notes
|
||||
* If the handler returns a Thenable, all kernel message processing pauses
|
||||
* until the Thenable is resolved.
|
||||
*/
|
||||
setStdInHandler(handler: MessageHandler<IStdinMessage>): void;
|
||||
|
||||
/**
|
||||
* Sets the iopub handler for the kernel future.
|
||||
*
|
||||
* #### Notes
|
||||
* If the handler returns a Thenable, all kernel message processing pauses
|
||||
* until the Thenable is resolved.
|
||||
*/
|
||||
setIOPubHandler(handler: MessageHandler<IIOPubMessage>): void;
|
||||
|
||||
/**
|
||||
* Register hook for IOPub messages.
|
||||
*
|
||||
* @param hook - The callback invoked for an IOPub message.
|
||||
*
|
||||
* #### Notes
|
||||
* The IOPub hook system allows you to preempt the handlers for IOPub
|
||||
* messages handled by the future.
|
||||
*
|
||||
* The most recently registered hook is run first. A hook can return a
|
||||
* boolean or a Thenable to a boolean, in which case all kernel message
|
||||
* processing pauses until the Thenable is fulfilled. If a hook return value
|
||||
* resolves to false, any later hooks will not run and the function will
|
||||
* return a Thenable resolving to false. If a hook throws an error, the error
|
||||
* is logged to the console and the next hook is run. If a hook is
|
||||
* registered during the hook processing, it will not run until the next
|
||||
* message. If a hook is removed during the hook processing, it will be
|
||||
* deactivated immediately.
|
||||
*/
|
||||
registerMessageHook(
|
||||
hook: (msg: IIOPubMessage) => boolean | Thenable<boolean>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Remove a hook for IOPub messages.
|
||||
*
|
||||
* @param hook - The hook to remove.
|
||||
*
|
||||
* #### Notes
|
||||
* If a hook is removed during the hook processing, it will be deactivated immediately.
|
||||
*/
|
||||
removeMessageHook(
|
||||
hook: (msg: IIOPubMessage) => boolean | Thenable<boolean>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Send an `input_reply` message.
|
||||
*/
|
||||
sendInputReply(content: IInputReply): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The valid channel names.
|
||||
*/
|
||||
export type Channel = 'shell' | 'iopub' | 'stdin';
|
||||
|
||||
/**
|
||||
* Kernel message header content.
|
||||
*
|
||||
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#general-message-format).
|
||||
*
|
||||
* **See also:** [[IMessage]]
|
||||
*/
|
||||
export interface IHeader {
|
||||
username: string;
|
||||
version: string;
|
||||
session: string;
|
||||
msg_id: string;
|
||||
msg_type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A kernel message
|
||||
*/
|
||||
export interface IMessage {
|
||||
type: Channel;
|
||||
header: IHeader;
|
||||
parent_header: IHeader | {};
|
||||
metadata: {};
|
||||
content: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* A kernel message on the `'shell'` channel.
|
||||
*/
|
||||
export interface IShellMessage extends IMessage {
|
||||
channel: 'shell';
|
||||
}
|
||||
|
||||
/**
|
||||
* A kernel message on the `'iopub'` channel.
|
||||
*/
|
||||
export interface IIOPubMessage extends IMessage {
|
||||
channel: 'iopub';
|
||||
}
|
||||
|
||||
/**
|
||||
* A kernel message on the `'stdin'` channel.
|
||||
*/
|
||||
export interface IStdinMessage extends IMessage {
|
||||
channel: 'stdin';
|
||||
}
|
||||
|
||||
/**
|
||||
* The content of an `'input_reply'` message.
|
||||
*/
|
||||
export interface IInputReply {
|
||||
value: string;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
76
src/sql/workbench/api/node/extHostNotebook.ts
Normal file
76
src/sql/workbench/api/node/extHostNotebook.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
|
||||
import { ExtHostNotebookShape, MainThreadNotebookShape, SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
|
||||
export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private readonly _proxy: MainThreadNotebookShape;
|
||||
private _providers = new Map<number, sqlops.nb.NotebookProvider>();
|
||||
|
||||
constructor(private _mainContext: IMainContext) {
|
||||
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
|
||||
}
|
||||
|
||||
//#region APIs called by main thread
|
||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<number> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
handleNotebookClosed(notebookUri: vscode.Uri): void {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region APIs called by extensions
|
||||
registerNotebookProvider(provider: sqlops.nb.NotebookProvider): vscode.Disposable {
|
||||
if (!provider || !provider.providerId) {
|
||||
throw new Error(localize('providerRequired', 'A NotebookProvider with valid providerId must be passed to this method'));
|
||||
}
|
||||
const handle = this._addNewProvider(provider);
|
||||
this._proxy.$registerNotebookProvider(provider.providerId, handle);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region private methods
|
||||
private _createDisposable(handle: number): Disposable {
|
||||
return new Disposable(() => {
|
||||
this._providers.delete(handle);
|
||||
this._proxy.$unregisterNotebookProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
private _nextHandle(): number {
|
||||
return ExtHostNotebook._handlePool++;
|
||||
}
|
||||
|
||||
private _withProvider<A, R>(handle: number, ctor: { new(...args: any[]): A }, callback: (adapter: A) => TPromise<R>): TPromise<R> {
|
||||
let provider = this._providers.get(handle);
|
||||
if (!(provider instanceof ctor)) {
|
||||
return TPromise.wrapError<R>(new Error('no adapter found'));
|
||||
}
|
||||
return callback(<any>provider);
|
||||
}
|
||||
|
||||
private _addNewProvider(adapter: sqlops.nb.NotebookProvider): number {
|
||||
const handle = this._nextHandle();
|
||||
this._providers.set(handle, adapter);
|
||||
return handle;
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
77
src/sql/workbench/api/node/mainThreadNotebook.ts
Normal file
77
src/sql/workbench/api/node/mainThreadNotebook.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { SqlExtHostContext, SqlMainContext, ExtHostNotebookShape, MainThreadNotebookShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { INotebookService, INotebookProvider, INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
|
||||
|
||||
private _proxy: ExtHostNotebookShape;
|
||||
private _registrations: { [handle: number]: NotebookProviderWrapper } = Object.create(null);
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@INotebookService private notebookService: INotebookService
|
||||
) {
|
||||
super();
|
||||
if (extHostContext) {
|
||||
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostNotebook);
|
||||
}
|
||||
}
|
||||
|
||||
//#region Extension host callable methods
|
||||
public $registerNotebookProvider(providerId: string, handle: number): void {
|
||||
let notebookProvider = new NotebookProviderWrapper(providerId, handle);
|
||||
this._registrations[providerId] = notebookProvider;
|
||||
this.notebookService.registerProvider(providerId, notebookProvider);
|
||||
}
|
||||
|
||||
public $unregisterNotebookProvider(handle: number): void {
|
||||
let registration = this._registrations[handle];
|
||||
if (registration) {
|
||||
this.notebookService.unregisterProvider(registration.providerId);
|
||||
registration.dispose();
|
||||
delete this._registrations[handle];
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}
|
||||
|
||||
class NotebookProviderWrapper extends Disposable implements INotebookProvider {
|
||||
|
||||
constructor(public readonly providerId, public readonly handle: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
|
||||
// TODO must call through to setup in the extension host
|
||||
return Promise.resolve(new NotebookManagerWrapper(this.providerId));
|
||||
}
|
||||
|
||||
handleNotebookClosed(notebookUri: URI): void {
|
||||
// TODO implement call through to extension host
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class NotebookManagerWrapper implements INotebookManager {
|
||||
constructor(public readonly providerId) {
|
||||
|
||||
}
|
||||
sessionManager: sqlops.nb.SessionManager;
|
||||
contentManager: sqlops.nb.ContentManager;
|
||||
serverManager: sqlops.nb.ServerManager;
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewD
|
||||
import { ExtHostModelViewTreeViews } from 'sql/workbench/api/node/extHostModelViewTree';
|
||||
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
|
||||
import { ExtHostBackgroundTaskManagement } from './extHostBackgroundTaskManagement';
|
||||
import { ExtHostNotebook } from 'sql/workbench/api/node/extHostNotebook';
|
||||
|
||||
export interface ISqlExtensionApiFactory {
|
||||
vsCodeFactory(extension: IExtensionDescription): typeof vscode;
|
||||
@@ -73,6 +74,7 @@ export function createApiFactory(
|
||||
const extHostDashboard = rpcProtocol.set(SqlExtHostContext.ExtHostDashboard, new ExtHostDashboard(rpcProtocol));
|
||||
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
|
||||
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
|
||||
const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol));
|
||||
|
||||
|
||||
return {
|
||||
@@ -402,6 +404,12 @@ export function createApiFactory(
|
||||
}
|
||||
};
|
||||
|
||||
const nb = {
|
||||
registerNotebookProvider(provider: sqlops.nb.NotebookProvider): vscode.Disposable {
|
||||
return extHostNotebook.registerNotebookProvider(provider);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
accounts,
|
||||
connection,
|
||||
@@ -437,7 +445,8 @@ export function createApiFactory(
|
||||
CardType: sqlExtHostTypes.CardType,
|
||||
Orientation: sqlExtHostTypes.Orientation,
|
||||
SqlThemeIcon: sqlExtHostTypes.SqlThemeIcon,
|
||||
TreeComponentItem: sqlExtHostTypes.TreeComponentItem
|
||||
TreeComponentItem: sqlExtHostTypes.TreeComponentItem,
|
||||
nb: nb
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,7 +23,8 @@ import 'sql/workbench/api/node/mainThreadDashboardWebview';
|
||||
import 'sql/workbench/api/node/mainThreadQueryEditor';
|
||||
import 'sql/workbench/api/node/mainThreadModelView';
|
||||
import 'sql/workbench/api/node/mainThreadModelViewDialog';
|
||||
import './mainThreadAccountManagement';
|
||||
import 'sql/workbench/api/node/mainThreadNotebook';
|
||||
import 'sql/workbench/api/node/mainThreadAccountManagement';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
export class SqlExtHostContribution implements IWorkbenchContribution {
|
||||
|
||||
@@ -545,6 +545,7 @@ export const SqlMainContext = {
|
||||
MainThreadDashboard: createMainId<MainThreadDashboardShape>('MainThreadDashboard'),
|
||||
MainThreadModelViewDialog: createMainId<MainThreadModelViewDialogShape>('MainThreadModelViewDialog'),
|
||||
MainThreadQueryEditor: createMainId<MainThreadQueryEditorShape>('MainThreadQueryEditor'),
|
||||
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook')
|
||||
};
|
||||
|
||||
export const SqlExtHostContext = {
|
||||
@@ -563,7 +564,8 @@ export const SqlExtHostContext = {
|
||||
ExtHostModelViewTreeViews: createExtId<ExtHostModelViewTreeViewsShape>('ExtHostModelViewTreeViews'),
|
||||
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
|
||||
ExtHostModelViewDialog: createExtId<ExtHostModelViewDialogShape>('ExtHostModelViewDialog'),
|
||||
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor')
|
||||
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor'),
|
||||
ExtHostNotebook: createExtId<ExtHostNotebookShape>('ExtHostNotebook')
|
||||
};
|
||||
|
||||
export interface MainThreadDashboardShape extends IDisposable {
|
||||
@@ -703,4 +705,21 @@ export interface ExtHostQueryEditorShape {
|
||||
export interface MainThreadQueryEditorShape extends IDisposable {
|
||||
$connect(fileUri: string, connectionId: string): Thenable<void>;
|
||||
$runQuery(fileUri: string): void;
|
||||
}
|
||||
|
||||
export interface ExtHostNotebookShape {
|
||||
|
||||
/**
|
||||
* Looks up a notebook manager for a given notebook URI
|
||||
* @param {vscode.Uri} notebookUri
|
||||
* @returns {Thenable<string>} handle of the manager to be used when sending
|
||||
*/
|
||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<number>;
|
||||
handleNotebookClosed(notebookUri: vscode.Uri): void;
|
||||
|
||||
}
|
||||
|
||||
export interface MainThreadNotebookShape extends IDisposable {
|
||||
$registerNotebookProvider(providerId: string, handle: number): void;
|
||||
$unregisterNotebookProvider(handle: number): void;
|
||||
}
|
||||
Reference in New Issue
Block a user