mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Integrate first SQL Notebooks Bits into Master (#3679)
* First crack tsql notebook (no output rendered yet) * getting messages back * intellisense working first cell, no connection errors * sql notebook cell output functioning * Latest SQL noteobook changes * Undo change to launch.json * Plumbing providers through * Kernels shown from multiple providers, can switch between them. No mementos yet * Ensure we have a feature flag for SQL notebooks, ensure existing functionality still works * Fix tslint duplicate imports issue * Addressing PR comments * second round of PR feedback to cleanup notebook service manager code * merge latest from master
This commit is contained in:
@@ -17,7 +17,7 @@ import { IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
|
|||||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||||
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
|
||||||
import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/services/notebook/notebookService';
|
import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/services/notebook/notebookService';
|
||||||
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
|
import { getProvidersForFileName } from 'sql/parts/notebook/notebookUtils';
|
||||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -63,13 +63,14 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
|
|||||||
if (uri && notebookValidator.isNotebookEnabled()) {
|
if (uri && notebookValidator.isNotebookEnabled()) {
|
||||||
return withService<INotebookService, NotebookInput>(instantiationService, INotebookService, notebookService => {
|
return withService<INotebookService, NotebookInput>(instantiationService, INotebookService, notebookService => {
|
||||||
let fileName: string = 'untitled';
|
let fileName: string = 'untitled';
|
||||||
let providerId: string = DEFAULT_NOTEBOOK_PROVIDER;
|
let providerIds: string[] = [DEFAULT_NOTEBOOK_PROVIDER];
|
||||||
if (input) {
|
if (input) {
|
||||||
fileName = input.getName();
|
fileName = input.getName();
|
||||||
providerId = getProviderForFileName(fileName, notebookService);
|
providerIds = getProvidersForFileName(fileName, notebookService);
|
||||||
}
|
}
|
||||||
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
|
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
|
||||||
notebookInputModel.providerId = providerId;
|
notebookInputModel.providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0];
|
||||||
|
notebookInputModel.providers = providerIds;
|
||||||
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
|
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
|
||||||
return notebookInput;
|
return notebookInput;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function parseNumAsTimeString(value: number, includeFraction: boolean = t
|
|||||||
return tempVal > 0 && includeFraction ? rs + '.' + mss : rs;
|
return tempVal > 0 && includeFraction ? rs + '.' + mss : rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): string {
|
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection' | 'notebook'): string {
|
||||||
let prefix = purpose ? uriPrefixes[purpose] : uriPrefixes.default;
|
let prefix = purpose ? uriPrefixes[purpose] : uriPrefixes.default;
|
||||||
let uri = generateUriWithPrefix(connection, prefix);
|
let uri = generateUriWithPrefix(connection, prefix);
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.cellModel) {
|
if (this.cellModel) {
|
||||||
this.cellModel.onOutputsChanged(() => {
|
this._register(this.cellModel.onOutputsChanged(() => {
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
|
|||||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||||
this.updateTheme(this.themeService.getColorTheme());
|
this.updateTheme(this.themeService.getColorTheme());
|
||||||
if (this.cellModel) {
|
if (this.cellModel) {
|
||||||
this.cellModel.onOutputsChanged(() => {
|
this._register(this.cellModel.onOutputsChanged(() => {
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,9 +91,9 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
this.setLoading(false);
|
this.setLoading(false);
|
||||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||||
this.updateTheme(this.themeService.getColorTheme());
|
this.updateTheme(this.themeService.getColorTheme());
|
||||||
this.cellModel.onOutputsChanged(e => {
|
this._register(this.cellModel.onOutputsChanged(e => {
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||||
|
|||||||
@@ -226,6 +226,9 @@ export class CellModel implements ICellModel {
|
|||||||
this._outputs.push(this.rewriteOutputUrls(output));
|
this._outputs.push(this.rewriteOutputUrls(output));
|
||||||
this.fireOutputsChanged();
|
this.fireOutputsChanged();
|
||||||
}
|
}
|
||||||
|
if (!this._future.inProgress) {
|
||||||
|
this._future.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
|
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
|
||||||
@@ -327,6 +330,7 @@ export class CellModel implements ICellModel {
|
|||||||
CellModel.LanguageMapping['pyspark3'] = 'python';
|
CellModel.LanguageMapping['pyspark3'] = 'python';
|
||||||
CellModel.LanguageMapping['python'] = 'python';
|
CellModel.LanguageMapping['python'] = 'python';
|
||||||
CellModel.LanguageMapping['scala'] = 'scala';
|
CellModel.LanguageMapping['scala'] = 'scala';
|
||||||
|
CellModel.LanguageMapping['sql'] = 'sql';
|
||||||
}
|
}
|
||||||
|
|
||||||
private get languageInfo(): nb.ILanguageInfo {
|
private get languageInfo(): nb.ILanguageInfo {
|
||||||
|
|||||||
@@ -249,9 +249,9 @@ export interface INotebookModel {
|
|||||||
readonly languageInfo: nb.ILanguageInfo;
|
readonly languageInfo: nb.ILanguageInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The notebook service used to call backend APIs
|
* All notebook managers applicable for a given notebook
|
||||||
*/
|
*/
|
||||||
readonly notebookManager: INotebookManager;
|
readonly notebookManagers: INotebookManager[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event fired on first initialization of the kernel and
|
* Event fired on first initialization of the kernel and
|
||||||
@@ -299,6 +299,11 @@ export interface INotebookModel {
|
|||||||
*/
|
*/
|
||||||
trustedMode: boolean;
|
trustedMode: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current notebook provider id
|
||||||
|
*/
|
||||||
|
providerId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the current kernel from the Kernel dropdown
|
* Change the current kernel from the Kernel dropdown
|
||||||
* @param displayName kernel name (as displayed in Kernel dropdown)
|
* @param displayName kernel name (as displayed in Kernel dropdown)
|
||||||
@@ -411,7 +416,8 @@ export interface INotebookModelOptions {
|
|||||||
*/
|
*/
|
||||||
factory: IModelFactory;
|
factory: IModelFactory;
|
||||||
|
|
||||||
notebookManager: INotebookManager;
|
notebookManagers: INotebookManager[];
|
||||||
|
providerId: string;
|
||||||
|
|
||||||
notificationService: INotificationService;
|
notificationService: INotificationService;
|
||||||
connectionService: IConnectionManagementService;
|
connectionService: IConnectionManagementService;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptio
|
|||||||
import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||||
import { nbversion } from '../notebookConstants';
|
import { nbversion } from '../notebookConstants';
|
||||||
import * as notebookUtils from '../notebookUtils';
|
import * as notebookUtils from '../notebookUtils';
|
||||||
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||||
import { SparkMagicContexts } from 'sql/parts/notebook/models/sparkMagicContexts';
|
import { SparkMagicContexts } from 'sql/parts/notebook/models/sparkMagicContexts';
|
||||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||||
@@ -45,7 +45,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||||
private _kernelsChangedEmitter = new Emitter<nb.IKernelSpec>();
|
private _kernelsChangedEmitter = new Emitter<nb.IKernelSpec>();
|
||||||
private _inErrorState: boolean = false;
|
private _inErrorState: boolean = false;
|
||||||
private _clientSession: IClientSession;
|
private _clientSessions: IClientSession[] = [];
|
||||||
|
private _activeClientSession: IClientSession;
|
||||||
private _sessionLoadFinished: Promise<void>;
|
private _sessionLoadFinished: Promise<void>;
|
||||||
private _onClientSessionReady = new Emitter<IClientSession>();
|
private _onClientSessionReady = new Emitter<IClientSession>();
|
||||||
private _activeContexts: IDefaultConnection;
|
private _activeContexts: IDefaultConnection;
|
||||||
@@ -60,20 +61,26 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
private _hadoopConnection: NotebookConnection;
|
private _hadoopConnection: NotebookConnection;
|
||||||
private _defaultKernel: nb.IKernelSpec;
|
private _defaultKernel: nb.IKernelSpec;
|
||||||
private _activeCell: ICellModel;
|
private _activeCell: ICellModel;
|
||||||
|
private _providerId: string;
|
||||||
|
|
||||||
constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) {
|
constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) {
|
||||||
super();
|
super();
|
||||||
if (!notebookOptions || !notebookOptions.notebookUri || !notebookOptions.notebookManager) {
|
if (!notebookOptions || !notebookOptions.notebookUri || !notebookOptions.notebookManagers) {
|
||||||
throw new Error('path or notebook service not defined');
|
throw new Error('path or notebook service not defined');
|
||||||
}
|
}
|
||||||
if (startSessionImmediately) {
|
if (startSessionImmediately) {
|
||||||
this.backgroundStartSession();
|
this.backgroundStartSession();
|
||||||
}
|
}
|
||||||
this._trustedMode = false;
|
this._trustedMode = false;
|
||||||
|
this._providerId = notebookOptions.providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get notebookManagers(): INotebookManager[] {
|
||||||
|
return this.notebookOptions.notebookManagers.filter(manager => manager.providerId !== DEFAULT_NOTEBOOK_PROVIDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get notebookManager(): INotebookManager {
|
public get notebookManager(): INotebookManager {
|
||||||
return this.notebookOptions.notebookManager;
|
return this.notebookManagers.find(manager => manager.providerId === this._providerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get notebookUri() : URI {
|
public get notebookUri() : URI {
|
||||||
@@ -93,7 +100,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get isSessionReady(): boolean {
|
public get isSessionReady(): boolean {
|
||||||
return !!this._clientSession;
|
return !!this._activeClientSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,11 +109,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
* notebook environment
|
* notebook environment
|
||||||
*/
|
*/
|
||||||
public get clientSession(): IClientSession {
|
public get clientSession(): IClientSession {
|
||||||
return this._clientSession;
|
return this._activeClientSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||||
return this.clientSession.kernelChanged;
|
return this._activeClientSession.kernelChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get kernelsChanged(): Event<nb.IKernelSpec> {
|
public get kernelsChanged(): Event<nb.IKernelSpec> {
|
||||||
@@ -130,7 +137,21 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get specs(): nb.IAllKernels | undefined {
|
public get specs(): nb.IAllKernels | undefined {
|
||||||
return this.notebookManager.sessionManager.specs;
|
let specs: nb.IAllKernels = {
|
||||||
|
defaultKernel: undefined,
|
||||||
|
kernels: []
|
||||||
|
};
|
||||||
|
this.notebookManagers.forEach(manager => {
|
||||||
|
if (manager.sessionManager && manager.sessionManager.specs && manager.sessionManager.specs.kernels) {
|
||||||
|
manager.sessionManager.specs.kernels.forEach(kernel => {
|
||||||
|
specs.kernels.push(kernel);
|
||||||
|
});
|
||||||
|
if (!specs.defaultKernel) {
|
||||||
|
specs.defaultKernel = manager.sessionManager.specs.defaultKernel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get inErrorState(): boolean {
|
public get inErrorState(): boolean {
|
||||||
@@ -145,6 +166,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
return this._trustedMode;
|
return this._trustedMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return this._providerId;
|
||||||
|
}
|
||||||
|
|
||||||
public set trustedMode(isTrusted: boolean) {
|
public set trustedMode(isTrusted: boolean) {
|
||||||
this._trustedMode = isTrusted;
|
this._trustedMode = isTrusted;
|
||||||
if (this._cells) {
|
if (this._cells) {
|
||||||
@@ -178,11 +203,16 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
this._trustedMode = isTrusted;
|
this._trustedMode = isTrusted;
|
||||||
let contents = null;
|
let contents = null;
|
||||||
if (this.notebookOptions.notebookUri.scheme !== Schemas.untitled) {
|
if (this.notebookOptions.notebookUri.scheme !== Schemas.untitled) {
|
||||||
contents = await this.notebookManager.contentManager.getNotebookContents(this.notebookOptions.notebookUri);
|
// TODO: separate ContentManager from NotebookManager
|
||||||
|
contents = await this.notebookManagers[0].contentManager.getNotebookContents(this.notebookOptions.notebookUri);
|
||||||
}
|
}
|
||||||
let factory = this.notebookOptions.factory;
|
let factory = this.notebookOptions.factory;
|
||||||
// if cells already exist, create them with language info (if it is saved)
|
// if cells already exist, create them with language info (if it is saved)
|
||||||
this._cells = undefined;
|
this._cells = undefined;
|
||||||
|
this._defaultLanguageInfo = {
|
||||||
|
name: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'sql' : 'python',
|
||||||
|
version: ''
|
||||||
|
};
|
||||||
if (contents) {
|
if (contents) {
|
||||||
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
|
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
|
||||||
this._savedKernelInfo = this.getSavedKernelInfo(contents);
|
this._savedKernelInfo = this.getSavedKernelInfo(contents);
|
||||||
@@ -288,11 +318,17 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public backgroundStartSession(): void {
|
public backgroundStartSession(): void {
|
||||||
this._clientSession = this.notebookOptions.factory.createClientSession({
|
// TODO: only one session should be active at a time, depending on the current provider
|
||||||
|
this.notebookManagers.forEach(manager => {
|
||||||
|
let clientSession = this.notebookOptions.factory.createClientSession({
|
||||||
notebookUri: this.notebookOptions.notebookUri,
|
notebookUri: this.notebookOptions.notebookUri,
|
||||||
notebookManager: this.notebookManager,
|
notebookManager: manager,
|
||||||
notificationService: this.notebookOptions.notificationService
|
notificationService: this.notebookOptions.notificationService
|
||||||
});
|
});
|
||||||
|
this._clientSessions.push(clientSession);
|
||||||
|
if (!this._activeClientSession) {
|
||||||
|
this._activeClientSession = clientSession;
|
||||||
|
}
|
||||||
let profile = this.connectionProfile as IConnectionProfile;
|
let profile = this.connectionProfile as IConnectionProfile;
|
||||||
|
|
||||||
if (this.isValidKnoxConnection(profile)) {
|
if (this.isValidKnoxConnection(profile)) {
|
||||||
@@ -301,17 +337,18 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
this._hadoopConnection = undefined;
|
this._hadoopConnection = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._clientSession.initialize(this._hadoopConnection);
|
clientSession.initialize(this._hadoopConnection);
|
||||||
this._sessionLoadFinished = this._clientSession.ready.then(async () => {
|
this._sessionLoadFinished = clientSession.ready.then(async () => {
|
||||||
if (this._clientSession.isInErrorState) {
|
if (clientSession.isInErrorState) {
|
||||||
this.setErrorState(this._clientSession.errorMessage);
|
this.setErrorState(clientSession.errorMessage);
|
||||||
} else {
|
} else {
|
||||||
this._onClientSessionReady.fire(this._clientSession);
|
this._onClientSessionReady.fire(clientSession);
|
||||||
// Once session is loaded, can use the session manager to retrieve useful info
|
// Once session is loaded, can use the session manager to retrieve useful info
|
||||||
this.loadKernelInfo();
|
this.loadKernelInfo();
|
||||||
await this.loadActiveContexts(undefined);
|
await this.loadActiveContexts(undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private isValidKnoxConnection(profile: IConnectionProfile | connection.Connection) {
|
private isValidKnoxConnection(profile: IConnectionProfile | connection.Connection) {
|
||||||
@@ -334,7 +371,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> {
|
public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> {
|
||||||
return this._clientSession.changeKernel(kernelSpec)
|
this.findProviderIdForKernel(kernelSpec);
|
||||||
|
return this._activeClientSession.changeKernel(kernelSpec)
|
||||||
.then((kernel) => {
|
.then((kernel) => {
|
||||||
kernel.ready.then(() => {
|
kernel.ready.then(() => {
|
||||||
if (kernel.info) {
|
if (kernel.info) {
|
||||||
@@ -359,7 +397,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
SparkMagicContexts.configureContext(this.notebookOptions);
|
SparkMagicContexts.configureContext(this.notebookOptions);
|
||||||
this._hadoopConnection = new NotebookConnection(newConnection);
|
this._hadoopConnection = new NotebookConnection(newConnection);
|
||||||
this.refreshConnections(newConnection);
|
this.refreshConnections(newConnection);
|
||||||
this._clientSession.updateConnection(this._hadoopConnection);
|
this._activeClientSession.updateConnection(this._hadoopConnection);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
let msg = notebookUtils.getErrorMessage(err);
|
let msg = notebookUtils.getErrorMessage(err);
|
||||||
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
||||||
@@ -381,21 +419,25 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private loadKernelInfo(): void {
|
private loadKernelInfo(): void {
|
||||||
this.clientSession.kernelChanged(async (e) => {
|
this._clientSessions.forEach(clientSession => {
|
||||||
|
clientSession.kernelChanged(async (e) => {
|
||||||
await this.loadActiveContexts(e);
|
await this.loadActiveContexts(e);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
let sessionManager = this.notebookManager.sessionManager;
|
let sessionManager = this.notebookManager.sessionManager;
|
||||||
if (sessionManager) {
|
if (sessionManager) {
|
||||||
let defaultKernel = SparkMagicContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo, this.notebookOptions.notificationService);
|
let defaultKernel = SparkMagicContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo, this.notebookOptions.notificationService);
|
||||||
this._defaultKernel = defaultKernel;
|
this._defaultKernel = defaultKernel;
|
||||||
this._clientSession.statusChanged(async (session) => {
|
this._clientSessions.forEach(clientSession => {
|
||||||
|
clientSession.statusChanged(async (session) => {
|
||||||
if (session && session.defaultKernelLoaded === true) {
|
if (session && session.defaultKernelLoaded === true) {
|
||||||
this._kernelsChangedEmitter.fire(defaultKernel);
|
this._kernelsChangedEmitter.fire(defaultKernel);
|
||||||
} else if (session && !session.defaultKernelLoaded) {
|
} else if (session && !session.defaultKernelLoaded) {
|
||||||
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
|
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
this.doChangeKernel(defaultKernel);
|
this.doChangeKernel(defaultKernel);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -408,9 +450,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
// Otherwise, default to python
|
// Otherwise, default to python
|
||||||
private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
|
private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
|
||||||
return notebook!.metadata!.language_info || {
|
return notebook!.metadata!.language_info || {
|
||||||
name: 'python',
|
name: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'sql' : 'python',
|
||||||
version: '',
|
version: '',
|
||||||
mimetype: 'x-python'
|
mimetype: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'x-sql' : 'x-python'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,9 +486,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
|
|
||||||
public async handleClosed(): Promise<void> {
|
public async handleClosed(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (this._clientSession) {
|
if (this._activeClientSession) {
|
||||||
await this._clientSession.shutdown();
|
await this._activeClientSession.shutdown();
|
||||||
this._clientSession = undefined;
|
this._clientSessions = undefined;
|
||||||
|
this._activeClientSession = undefined;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.notifyError(localize('shutdownError', 'An error occurred when closing the notebook: {0}', err));
|
this.notifyError(localize('shutdownError', 'An error occurred when closing the notebook: {0}', err));
|
||||||
@@ -482,7 +525,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
if (!notebook) {
|
if (!notebook) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await this.notebookManager.contentManager.save(this.notebookOptions.notebookUri, notebook);
|
// TODO: refactor ContentManager out from NotebookManager
|
||||||
|
await this.notebookManagers[0].contentManager.save(this.notebookOptions.notebookUri, notebook);
|
||||||
this._contentChangedEmitter.fire({
|
this._contentChangedEmitter.fire({
|
||||||
changeType: NotebookChangeType.DirtyStateChanged,
|
changeType: NotebookChangeType.DirtyStateChanged,
|
||||||
isDirty: false
|
isDirty: false
|
||||||
@@ -504,6 +548,23 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set _providerId and _activeClientSession based on a kernelSpec representing new kernel
|
||||||
|
* @param kernelSpec KernelSpec for new kernel
|
||||||
|
*/
|
||||||
|
private findProviderIdForKernel(kernelSpec: nb.IKernelSpec): void {
|
||||||
|
for (let i = 0; i < this.notebookManagers.length; i++) {
|
||||||
|
if (this.notebookManagers[i].sessionManager && this.notebookManagers[i].sessionManager.specs && this.notebookManagers[i].sessionManager.specs.kernels) {
|
||||||
|
let index = this.notebookManagers[i].sessionManager.specs.kernels.findIndex(kernel => kernel.name === kernelSpec.name);
|
||||||
|
if (index >= 0) {
|
||||||
|
this._activeClientSession = this._clientSessions[i];
|
||||||
|
this._providerId = this.notebookManagers[i].providerId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Serialize the model to JSON.
|
* Serialize the model to JSON.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
|
|||||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||||
import { ICellModel, IModelFactory, notebookConstants, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, IModelFactory, notebookConstants, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
||||||
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||||
@@ -64,7 +64,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
private _errorMessage: string;
|
private _errorMessage: string;
|
||||||
protected _actionBar: Taskbar;
|
protected _actionBar: Taskbar;
|
||||||
protected isLoading: boolean;
|
protected isLoading: boolean;
|
||||||
private notebookManager: INotebookManager;
|
private notebookManagers: INotebookManager[] = [];
|
||||||
private _modelReadyDeferred = new Deferred<NotebookModel>();
|
private _modelReadyDeferred = new Deferred<NotebookModel>();
|
||||||
private _modelRegisteredDeferred = new Deferred<NotebookModel>();
|
private _modelRegisteredDeferred = new Deferred<NotebookModel>();
|
||||||
private profile: IConnectionProfile;
|
private profile: IConnectionProfile;
|
||||||
@@ -231,13 +231,17 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
|
|
||||||
private async loadModel(): Promise<void> {
|
private async loadModel(): Promise<void> {
|
||||||
await this.awaitNonDefaultProvider();
|
await this.awaitNonDefaultProvider();
|
||||||
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this._notebookParams.providerId, this._notebookParams.notebookUri);
|
for (let providerId of this._notebookParams.providers) {
|
||||||
|
let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri);
|
||||||
|
this.notebookManagers.push(notebookManager);
|
||||||
|
}
|
||||||
let model = new NotebookModel({
|
let model = new NotebookModel({
|
||||||
factory: this.modelFactory,
|
factory: this.modelFactory,
|
||||||
notebookUri: this._notebookParams.notebookUri,
|
notebookUri: this._notebookParams.notebookUri,
|
||||||
connectionService: this.connectionManagementService,
|
connectionService: this.connectionManagementService,
|
||||||
notificationService: this.notificationService,
|
notificationService: this.notificationService,
|
||||||
notebookManager: this.notebookManager
|
notebookManagers: this.notebookManagers,
|
||||||
|
providerId: notebookUtils.sqlNotebooksEnabled() ? 'sql' : 'jupyter' // this is tricky; really should also depend on the connection profile
|
||||||
}, false, this.profile);
|
}, false, this.profile);
|
||||||
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
|
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
|
||||||
await model.requestModelLoad(this._notebookParams.isTrusted);
|
await model.requestModelLoad(this._notebookParams.isTrusted);
|
||||||
@@ -258,7 +262,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
await this.notebookService.registrationComplete;
|
await this.notebookService.registrationComplete;
|
||||||
// Refresh the provider if we had been using default
|
// Refresh the provider if we had been using default
|
||||||
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) {
|
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) {
|
||||||
this._notebookParams.providerId = notebookUtils.getProviderForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService);
|
let providers= notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService);
|
||||||
|
let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER);
|
||||||
|
this._notebookParams.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0];
|
||||||
}
|
}
|
||||||
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) {
|
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) {
|
||||||
// If it's still the default, warn them they should install an extension
|
// If it's still the default, warn them they should install an extension
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export class NotebookEditor extends BaseEditor {
|
|||||||
notebookUri: input.notebookUri,
|
notebookUri: input.notebookUri,
|
||||||
input: input,
|
input: input,
|
||||||
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
|
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
|
||||||
|
providers: input.providers ? input.providers : [DEFAULT_NOTEBOOK_PROVIDER],
|
||||||
isTrusted: input.isTrusted
|
isTrusted: input.isTrusted
|
||||||
};
|
};
|
||||||
bootstrapAngular(this.instantiationService,
|
bootstrapAngular(this.instantiationService,
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ export class NotebookInputModel extends EditorModel {
|
|||||||
private dirty: boolean;
|
private dirty: boolean;
|
||||||
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||||
private _providerId: string;
|
private _providerId: string;
|
||||||
constructor(public readonly notebookUri: URI, private readonly handle: number, private _isTrusted: boolean = false, private saveHandler?: ModeViewSaveHandler) {
|
constructor(public readonly notebookUri: URI, private readonly handle: number, private _isTrusted: boolean = false, private saveHandler?: ModeViewSaveHandler, provider?: string, private _providers?: string[]) {
|
||||||
super();
|
super();
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
|
this._providerId = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providerId(): string {
|
public get providerId(): string {
|
||||||
@@ -37,6 +38,14 @@ export class NotebookInputModel extends EditorModel {
|
|||||||
this._providerId = value;
|
this._providerId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get providers(): string[] {
|
||||||
|
return this._providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set providers(value: string[]) {
|
||||||
|
this._providers = value;
|
||||||
|
}
|
||||||
|
|
||||||
get isTrusted(): boolean {
|
get isTrusted(): boolean {
|
||||||
return this._isTrusted;
|
return this._isTrusted;
|
||||||
}
|
}
|
||||||
@@ -99,6 +108,10 @@ export class NotebookInput extends EditorInput {
|
|||||||
return this._model.providerId;
|
return this._model.providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get providers(): string[] {
|
||||||
|
return this._model.providers;
|
||||||
|
}
|
||||||
|
|
||||||
public getTypeId(): string {
|
public getTypeId(): string {
|
||||||
return NotebookInput.ID;
|
return NotebookInput.ID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,21 +39,26 @@ export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Pr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProviderForFileName(fileName: string, notebookService: INotebookService): string {
|
export function getProvidersForFileName(fileName: string, notebookService: INotebookService): string[] {
|
||||||
let fileExt = path.extname(fileName);
|
let fileExt = path.extname(fileName);
|
||||||
let provider: string;
|
let providers: string[];
|
||||||
// First try to get provider for actual file type
|
// First try to get provider for actual file type
|
||||||
if (fileExt && fileExt.startsWith('.')) {
|
if (fileExt && fileExt.startsWith('.')) {
|
||||||
fileExt = fileExt.slice(1,fileExt.length);
|
fileExt = fileExt.slice(1,fileExt.length);
|
||||||
provider = notebookService.getProviderForFileType(fileExt);
|
providers = notebookService.getProvidersForFileType(fileExt);
|
||||||
}
|
}
|
||||||
// Fallback to provider for default file type (assume this is a global handler)
|
// Fallback to provider for default file type (assume this is a global handler)
|
||||||
if (!provider) {
|
if (!providers) {
|
||||||
provider = notebookService.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
|
providers = notebookService.getProvidersForFileType(DEFAULT_NOTEBOOK_FILETYPE);
|
||||||
}
|
}
|
||||||
// Finally if all else fails, use the built-in handler
|
// Finally if all else fails, use the built-in handler
|
||||||
if (!provider) {
|
if (!providers) {
|
||||||
provider = DEFAULT_NOTEBOOK_PROVIDER;
|
providers = [DEFAULT_NOTEBOOK_PROVIDER];
|
||||||
}
|
}
|
||||||
return provider;
|
return providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private feature flag to enable Sql Notebook experience
|
||||||
|
export function sqlNotebooksEnabled() {
|
||||||
|
return process.env['SQLOPS_SQL_NOTEBOOK'] !== undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ export const Extensions = {
|
|||||||
export interface NotebookProviderRegistration {
|
export interface NotebookProviderRegistration {
|
||||||
provider: string;
|
provider: string;
|
||||||
fileExtensions: string | string[];
|
fileExtensions: string | string[];
|
||||||
|
standardKernels: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
let notebookProviderType: IJSONSchema = {
|
let notebookProviderType: IJSONSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
default: { provider: '', fileExtensions: [] },
|
default: { provider: '', fileExtensions: [], standardKernels: [] },
|
||||||
properties: {
|
properties: {
|
||||||
provider: {
|
provider: {
|
||||||
description: localize('carbon.extension.contributes.notebook.provider', 'Identifier of the notebook provider.'),
|
description: localize('carbon.extension.contributes.notebook.provider', 'Identifier of the notebook provider.'),
|
||||||
@@ -39,6 +40,18 @@ let notebookProviderType: IJSONSchema = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
standardKernels: {
|
||||||
|
description: localize('carbon.extension.contributes.notebook.standardKernels', 'What kernels should be standard with this notebook provider'),
|
||||||
|
oneOf: [
|
||||||
|
{ type: 'string' },
|
||||||
|
{
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
|||||||
|
|
||||||
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
||||||
export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
|
export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
|
||||||
|
export const SQL_NOTEBOOK_PROVIDER = 'sql';
|
||||||
|
|
||||||
export interface INotebookService {
|
export interface INotebookService {
|
||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
@@ -45,7 +46,7 @@ export interface INotebookService {
|
|||||||
|
|
||||||
getSupportedFileExtensions(): string[];
|
getSupportedFileExtensions(): string[];
|
||||||
|
|
||||||
getProviderForFileType(fileType: string): string;
|
getProvidersForFileType(fileType: string): string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
|
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
|
||||||
@@ -86,6 +87,7 @@ export interface INotebookParams extends IBootstrapParams {
|
|||||||
notebookUri: URI;
|
notebookUri: URI;
|
||||||
input: NotebookInput;
|
input: NotebookInput;
|
||||||
providerId: string;
|
providerId: string;
|
||||||
|
providers: string[];
|
||||||
isTrusted: boolean;
|
isTrusted: boolean;
|
||||||
profile?: IConnectionProfile;
|
profile?: IConnectionProfile;
|
||||||
modelFactory?: ModelFactory;
|
modelFactory?: ModelFactory;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER,
|
INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER,
|
||||||
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor
|
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER
|
||||||
} from 'sql/services/notebook/notebookService';
|
} from 'sql/services/notebook/notebookService';
|
||||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||||
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
|
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
|
||||||
@@ -27,6 +27,9 @@ import { IExtensionManagementService, IExtensionIdentifier } from 'vs/platform/e
|
|||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
import { getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||||
import { Deferred } from 'sql/base/common/promise';
|
import { Deferred } from 'sql/base/common/promise';
|
||||||
|
import { SqlSessionManager } from 'sql/services/notebook/sqlSessionManager';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { sqlNotebooksEnabled } from 'sql/parts/notebook/notebookUtils';
|
||||||
|
|
||||||
export interface NotebookProviderProperties {
|
export interface NotebookProviderProperties {
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -70,24 +73,25 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||||||
private _memento = new Memento('notebookProviders');
|
private _memento = new Memento('notebookProviders');
|
||||||
private _mimeRegistry: RenderMimeRegistry;
|
private _mimeRegistry: RenderMimeRegistry;
|
||||||
private _providers: Map<string, ProviderDescriptor> = new Map();
|
private _providers: Map<string, ProviderDescriptor> = new Map();
|
||||||
private _managers: Map<string, INotebookManager> = new Map();
|
private _managersMap: Map<string, INotebookManager[]> = new Map();
|
||||||
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
|
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
|
||||||
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
|
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
|
||||||
private _onCellChanged = new Emitter<INotebookEditor>();
|
private _onCellChanged = new Emitter<INotebookEditor>();
|
||||||
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
|
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
|
||||||
private _editors = new Map<string, INotebookEditor>();
|
private _editors = new Map<string, INotebookEditor>();
|
||||||
private _fileToProviders = new Map<string, NotebookProviderRegistration>();
|
private _fileToProviders = new Map<string, NotebookProviderRegistration[]>();
|
||||||
private _registrationComplete = new Deferred<void>();
|
private _registrationComplete = new Deferred<void>();
|
||||||
private _isRegistrationComplete = false;
|
private _isRegistrationComplete = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IStorageService private _storageService: IStorageService,
|
@IStorageService private _storageService: IStorageService,
|
||||||
@IExtensionService extensionService: IExtensionService,
|
@IExtensionService extensionService: IExtensionService,
|
||||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService
|
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||||
|
@IInstantiationService private _instantiationService: IInstantiationService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._register(notebookRegistry.onNewRegistration(this.updateRegisteredProviders, this));
|
this._register(notebookRegistry.onNewRegistration(this.updateRegisteredProviders, this));
|
||||||
this.registerDefaultProvider();
|
this.registerBuiltInProvider();
|
||||||
|
|
||||||
if (extensionService) {
|
if (extensionService) {
|
||||||
extensionService.whenInstalledExtensionsRegistered().then(() => {
|
extensionService.whenInstalledExtensionsRegistered().then(() => {
|
||||||
@@ -142,26 +146,34 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private addFileProvider(fileType: string, provider: NotebookProviderRegistration) {
|
private addFileProvider(fileType: string, provider: NotebookProviderRegistration) {
|
||||||
this._fileToProviders.set(fileType.toUpperCase(), provider);
|
let providers = this._fileToProviders.get(fileType.toUpperCase());
|
||||||
|
if (!providers) {
|
||||||
|
providers = [];
|
||||||
|
}
|
||||||
|
providers.push(provider);
|
||||||
|
this._fileToProviders.set(fileType.toUpperCase(), providers);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportedFileExtensions(): string[] {
|
getSupportedFileExtensions(): string[] {
|
||||||
return Array.from(this._fileToProviders.keys());
|
return Array.from(this._fileToProviders.keys());
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderForFileType(fileType: string): string {
|
getProvidersForFileType(fileType: string): string[] {
|
||||||
fileType = fileType.toUpperCase();
|
fileType = fileType.toUpperCase();
|
||||||
let provider = this._fileToProviders.get(fileType);
|
let providers = this._fileToProviders.get(fileType);
|
||||||
return provider ? provider.provider : undefined;
|
|
||||||
|
return providers ? providers.map(provider => provider.provider) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public shutdown(): void {
|
public shutdown(): void {
|
||||||
this._managers.forEach(manager => {
|
this._managersMap.forEach(manager => {
|
||||||
if (manager.serverManager) {
|
manager.forEach(m => {
|
||||||
|
if (m.serverManager) {
|
||||||
// TODO should this thenable be awaited?
|
// TODO should this thenable be awaited?
|
||||||
manager.serverManager.stopServer();
|
m.serverManager.stopServer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrCreateNotebookManager(providerId: string, uri: URI): Promise<INotebookManager> {
|
async getOrCreateNotebookManager(providerId: string, uri: URI): Promise<INotebookManager> {
|
||||||
@@ -169,14 +181,20 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||||||
throw new Error(localize('notebookUriNotDefined', 'No URI was passed when creating a notebook manager'));
|
throw new Error(localize('notebookUriNotDefined', 'No URI was passed when creating a notebook manager'));
|
||||||
}
|
}
|
||||||
let uriString = uri.toString();
|
let uriString = uri.toString();
|
||||||
let manager = this._managers.get(uriString);
|
let managers: INotebookManager[] = this._managersMap.get(uriString);
|
||||||
if (!manager) {
|
// If manager already exists for a given notebook, return it
|
||||||
manager = await this.doWithProvider(providerId, (provider) => provider.getNotebookManager(uri));
|
if (managers) {
|
||||||
if (manager) {
|
let index = managers.findIndex(m => m.providerId === providerId);
|
||||||
this._managers.set(uriString, manager);
|
if (index && index >= 0) {
|
||||||
|
return managers[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return manager;
|
let newManager = await this.doWithProvider(providerId, (provider) => provider.getNotebookManager(uri));
|
||||||
|
|
||||||
|
managers = managers || [];
|
||||||
|
managers.push(newManager);
|
||||||
|
this._managersMap.set(uriString, managers);
|
||||||
|
return newManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
get onNotebookEditorAdd(): Event<INotebookEditor> {
|
get onNotebookEditorAdd(): Event<INotebookEditor> {
|
||||||
@@ -226,12 +244,14 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||||||
private sendNotebookCloseToProvider(editor: INotebookEditor): void {
|
private sendNotebookCloseToProvider(editor: INotebookEditor): void {
|
||||||
let notebookUri = editor.notebookParams.notebookUri;
|
let notebookUri = editor.notebookParams.notebookUri;
|
||||||
let uriString = notebookUri.toString();
|
let uriString = notebookUri.toString();
|
||||||
let manager = this._managers.get(uriString);
|
let manager = this._managersMap.get(uriString);
|
||||||
if (manager) {
|
if (manager) {
|
||||||
// As we have a manager, we can assume provider is ready
|
// As we have a manager, we can assume provider is ready
|
||||||
this._managers.delete(uriString);
|
this._managersMap.delete(uriString);
|
||||||
let provider = this._providers.get(manager.providerId);
|
manager.forEach(m => {
|
||||||
|
let provider = this._providers.get(m.providerId);
|
||||||
provider.instance.handleNotebookClosed(notebookUri);
|
provider.instance.handleNotebookClosed(notebookUri);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,13 +323,24 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerDefaultProvider() {
|
private registerBuiltInProvider() {
|
||||||
|
if (!sqlNotebooksEnabled()) {
|
||||||
let defaultProvider = new BuiltinProvider();
|
let defaultProvider = new BuiltinProvider();
|
||||||
this.registerProvider(defaultProvider.providerId, defaultProvider);
|
this.registerProvider(defaultProvider.providerId, defaultProvider);
|
||||||
notebookRegistry.registerNotebookProvider({
|
notebookRegistry.registerNotebookProvider({
|
||||||
provider: defaultProvider.providerId,
|
provider: defaultProvider.providerId,
|
||||||
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE
|
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
|
||||||
|
standardKernels: []
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
let sqlProvider = new SqlNotebookProvider(this._instantiationService);
|
||||||
|
this.registerProvider(sqlProvider.providerId, sqlProvider);
|
||||||
|
notebookRegistry.registerNotebookProvider({
|
||||||
|
provider: sqlProvider.providerId,
|
||||||
|
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
|
||||||
|
standardKernels: ['SQL']
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService) {
|
private removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService) {
|
||||||
@@ -330,6 +361,7 @@ export class BuiltinProvider implements INotebookProvider {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.manager = new BuiltInNotebookManager();
|
this.manager = new BuiltInNotebookManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providerId(): string {
|
public get providerId(): string {
|
||||||
return DEFAULT_NOTEBOOK_PROVIDER;
|
return DEFAULT_NOTEBOOK_PROVIDER;
|
||||||
}
|
}
|
||||||
@@ -350,6 +382,7 @@ export class BuiltInNotebookManager implements INotebookManager {
|
|||||||
this._contentManager = new LocalContentManager();
|
this._contentManager = new LocalContentManager();
|
||||||
this._sessionManager = new SessionManager();
|
this._sessionManager = new SessionManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providerId(): string {
|
public get providerId(): string {
|
||||||
return DEFAULT_NOTEBOOK_PROVIDER;
|
return DEFAULT_NOTEBOOK_PROVIDER;
|
||||||
}
|
}
|
||||||
@@ -367,3 +400,50 @@ export class BuiltInNotebookManager implements INotebookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SqlNotebookProvider implements INotebookProvider {
|
||||||
|
private manager: SqlNotebookManager;
|
||||||
|
|
||||||
|
constructor(private _instantiationService: IInstantiationService) {
|
||||||
|
this.manager = new SqlNotebookManager(this._instantiationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return SQL_NOTEBOOK_PROVIDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
|
||||||
|
return Promise.resolve(this.manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNotebookClosed(notebookUri: URI): void {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SqlNotebookManager implements INotebookManager {
|
||||||
|
private _contentManager: nb.ContentManager;
|
||||||
|
private _sessionManager: nb.SessionManager;
|
||||||
|
|
||||||
|
constructor(private _instantiationService: IInstantiationService) {
|
||||||
|
this._contentManager = new LocalContentManager();
|
||||||
|
this._sessionManager = new SqlSessionManager(this._instantiationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return SQL_NOTEBOOK_PROVIDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get contentManager(): nb.ContentManager {
|
||||||
|
return this._contentManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get serverManager(): nb.ServerManager {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sessionManager(): nb.SessionManager {
|
||||||
|
return this._sessionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { nb } from 'sqlops';
|
import { nb } from 'sqlops';
|
||||||
|
|||||||
325
src/sql/services/notebook/sqlSessionManager.ts
Normal file
325
src/sql/services/notebook/sqlSessionManager.ts
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, QueryExecuteSubsetResult, IDbColumn, DbCellValue } from 'sqlops';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
import QueryRunner, { EventType } from 'sql/parts/query/execution/queryRunner';
|
||||||
|
import { IConnectionManagementService, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import Severity from 'vs/base/common/severity';
|
||||||
|
import * as Utils from 'sql/parts/connection/common/utils';
|
||||||
|
import { Deferred } from 'sql/base/common/promise';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { mssqlProviderName } from 'sql/parts/connection/common/constants';
|
||||||
|
|
||||||
|
export const sqlKernel: string = localize('sqlKernel', 'SQL');
|
||||||
|
export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error");
|
||||||
|
|
||||||
|
let sqlKernelSpec: nb.IKernelSpec = ({
|
||||||
|
name: sqlKernel,
|
||||||
|
language: 'sql',
|
||||||
|
display_name: sqlKernel
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface SQLData {
|
||||||
|
columns: Array<string>;
|
||||||
|
rows: Array<Array<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SqlSessionManager implements nb.SessionManager {
|
||||||
|
constructor(private _instantiationService: IInstantiationService) {}
|
||||||
|
|
||||||
|
public get isReady(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get ready(): Thenable<void> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get specs(): nb.IAllKernels {
|
||||||
|
let allKernels: nb.IAllKernels = {
|
||||||
|
defaultKernel: sqlKernel,
|
||||||
|
kernels: [sqlKernelSpec]
|
||||||
|
};
|
||||||
|
return allKernels;
|
||||||
|
}
|
||||||
|
|
||||||
|
startNew(options: nb.ISessionOptions): Thenable<nb.ISession> {
|
||||||
|
let session = new SqlSession(options, this._instantiationService);
|
||||||
|
return Promise.resolve(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown(id: string): Thenable<void> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SqlSession implements nb.ISession {
|
||||||
|
private _kernel: SqlKernel;
|
||||||
|
private _defaultKernelLoaded = false;
|
||||||
|
|
||||||
|
public set defaultKernelLoaded(value) {
|
||||||
|
this._defaultKernelLoaded = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get defaultKernelLoaded(): boolean {
|
||||||
|
return this._defaultKernelLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private options: nb.ISessionOptions, private _instantiationService: IInstantiationService) {
|
||||||
|
this._kernel = this._instantiationService.createInstance(SqlKernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get canChangeKernels(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): string {
|
||||||
|
return this.options.kernelId || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get path(): string {
|
||||||
|
return this.options.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): string {
|
||||||
|
return this.options.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get type(): string {
|
||||||
|
return this.options.type || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get status(): nb.KernelStatus {
|
||||||
|
return 'connected';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get kernel(): nb.IKernel {
|
||||||
|
return this._kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
|
||||||
|
return Promise.resolve(this.kernel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SqlKernel extends Disposable implements nb.IKernel {
|
||||||
|
private _queryRunner: QueryRunner;
|
||||||
|
private _columns: IDbColumn[];
|
||||||
|
private _rows: DbCellValue[][];
|
||||||
|
|
||||||
|
constructor(@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||||
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
|
@IErrorMessageService private _errorMessageService: IErrorMessageService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): string {
|
||||||
|
return '-1';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): string {
|
||||||
|
return sqlKernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get supportsIntellisense(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isReady(): boolean {
|
||||||
|
// should we be checking on the tools service status here?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get ready(): Thenable<void> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get info(): nb.IInfoReply {
|
||||||
|
let info: nb.IInfoReply = {
|
||||||
|
protocol_version: '',
|
||||||
|
implementation: '',
|
||||||
|
implementation_version: '',
|
||||||
|
language_info: {
|
||||||
|
name: 'sql',
|
||||||
|
version: '',
|
||||||
|
},
|
||||||
|
banner: '',
|
||||||
|
help_links: [{
|
||||||
|
text: '',
|
||||||
|
url: ''
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
getSpec(): Thenable<nb.IKernelSpec> {
|
||||||
|
return Promise.resolve(sqlKernelSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture {
|
||||||
|
if (this._queryRunner) {
|
||||||
|
this._queryRunner.runQuery(content.code);
|
||||||
|
} else {
|
||||||
|
let connections = this._connectionManagementService.getActiveConnections();
|
||||||
|
let connectionProfile = connections.find(connection => connection.providerName === mssqlProviderName);
|
||||||
|
let connectionUri = Utils.generateUri(connectionProfile, 'notebook');
|
||||||
|
this._queryRunner = this._instantiationService.createInstance(QueryRunner, connectionUri, undefined);
|
||||||
|
this._connectionManagementService.connect(connectionProfile, connectionUri).then((result) =>
|
||||||
|
{
|
||||||
|
this.addQueryEventListeners(this._queryRunner);
|
||||||
|
this._queryRunner.runQuery(content.code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SQLFuture(this._queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestComplete(content: nb.ICompleteRequest): Thenable<nb.ICompleteReplyMsg> {
|
||||||
|
let response: Partial<nb.ICompleteReplyMsg> = { };
|
||||||
|
return Promise.resolve(response as nb.ICompleteReplyMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
interrupt(): Thenable<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addQueryEventListeners(queryRunner: QueryRunner): void {
|
||||||
|
this._register(queryRunner.addListener(EventType.COMPLETE, () => {
|
||||||
|
this.queryComplete().catch(error => {
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, sqlKernelError, error);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
this._register(queryRunner.addListener(EventType.MESSAGE, message => {
|
||||||
|
if (message.isError) {
|
||||||
|
this._errorMessageService.showDialog(Severity.Error, sqlKernelError, message.message);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async queryComplete(): Promise<void> {
|
||||||
|
let batches = this._queryRunner.batchSets;
|
||||||
|
// currently only support 1 batch set 1 resultset
|
||||||
|
if (batches.length > 0) {
|
||||||
|
let batch = batches[0];
|
||||||
|
if (batch.resultSetSummaries.length > 0
|
||||||
|
&& batch.resultSetSummaries[0].rowCount > 0
|
||||||
|
) {
|
||||||
|
let resultset = batch.resultSetSummaries[0];
|
||||||
|
this._columns = resultset.columnInfo;
|
||||||
|
let rows: QueryExecuteSubsetResult;
|
||||||
|
try {
|
||||||
|
rows = await this._queryRunner.getQueryRows(0, resultset.rowCount, batch.id, resultset.id);
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
this._rows = rows.resultSubset.rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO issue #2746 should ideally show a warning inside the dialog if have no data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SQLFuture extends Disposable implements FutureInternal {
|
||||||
|
private _msg: nb.IMessage = undefined;
|
||||||
|
|
||||||
|
constructor(private _queryRunner: QueryRunner) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
get inProgress(): boolean {
|
||||||
|
return !this._queryRunner.hasCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
get msg(): nb.IMessage {
|
||||||
|
return this._msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
get done(): Thenable<nb.IShellMessage> {
|
||||||
|
let deferred = new Deferred<nb.IShellMessage> ();
|
||||||
|
try {
|
||||||
|
this._register(this._queryRunner.onBatchEnd(e => {
|
||||||
|
let msg: nb.IShellMessage = {
|
||||||
|
channel: 'shell',
|
||||||
|
type: 'execute_reply',
|
||||||
|
content: { status: 'ok' },
|
||||||
|
header: undefined,
|
||||||
|
metadata: {},
|
||||||
|
parent_header: undefined
|
||||||
|
};
|
||||||
|
this._msg = msg;
|
||||||
|
deferred.resolve(msg);
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendInputReply(content: nb.IInputReply): void {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
setReplyHandler(handler: nb.MessageHandler<nb.IShellMessage>): void {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||||
|
this._register(this._queryRunner.onBatchEnd(batch => {
|
||||||
|
this._queryRunner.getQueryRows(0, batch.resultSetSummaries[0].rowCount, 0, 0).then(d => {
|
||||||
|
let data:SQLData = {
|
||||||
|
columns: batch.resultSetSummaries[0].columnInfo.map(c => c.columnName),
|
||||||
|
rows: d.resultSubset.rows.map(r => r.map(c => c.displayValue))
|
||||||
|
};
|
||||||
|
let table: HTMLTableElement = document.createElement('table');
|
||||||
|
table.createTHead();
|
||||||
|
table.createTBody();
|
||||||
|
let hrow = <HTMLTableRowElement>table.insertRow();
|
||||||
|
// headers
|
||||||
|
for (let column of data.columns) {
|
||||||
|
var cell = hrow.insertCell();
|
||||||
|
cell.innerHTML = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let row in data.rows) {
|
||||||
|
let hrow = <HTMLTableRowElement>table.insertRow();
|
||||||
|
for (let column in data.columns) {
|
||||||
|
var cell = hrow.insertCell();
|
||||||
|
cell.innerHTML = data.rows[row][column];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tableHtml = '<table>' + table.innerHTML + '</table>';
|
||||||
|
|
||||||
|
let msg: nb.IIOPubMessage = {
|
||||||
|
channel: 'iopub',
|
||||||
|
type: 'iopub',
|
||||||
|
header: <nb.IHeader> {
|
||||||
|
msg_id: undefined,
|
||||||
|
msg_type: 'execute_result'
|
||||||
|
},
|
||||||
|
content: <nb.IExecuteResult> {
|
||||||
|
output_type: 'execute_result',
|
||||||
|
metadata: {},
|
||||||
|
execution_count: 0,
|
||||||
|
data: { 'text/html' : tableHtml},
|
||||||
|
},
|
||||||
|
metadata: undefined,
|
||||||
|
parent_header: undefined
|
||||||
|
};
|
||||||
|
handler.handle(msg);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/sql/sqlops.proposed.d.ts
vendored
3
src/sql/sqlops.proposed.d.ts
vendored
@@ -1655,6 +1655,7 @@ declare module 'sqlops' {
|
|||||||
|
|
||||||
export interface NotebookProvider {
|
export interface NotebookProvider {
|
||||||
readonly providerId: string;
|
readonly providerId: string;
|
||||||
|
readonly standardKernels: string[];
|
||||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<NotebookManager>;
|
getNotebookManager(notebookUri: vscode.Uri): Thenable<NotebookManager>;
|
||||||
handleNotebookClosed(notebookUri: vscode.Uri): void;
|
handleNotebookClosed(notebookUri: vscode.Uri): void;
|
||||||
}
|
}
|
||||||
@@ -2259,7 +2260,7 @@ declare module 'sqlops' {
|
|||||||
/**
|
/**
|
||||||
* The valid channel names.
|
* The valid channel names.
|
||||||
*/
|
*/
|
||||||
export type Channel = 'shell' | 'iopub' | 'stdin';
|
export type Channel = 'shell' | 'iopub' | 'stdin' | 'execute_reply';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kernel message header content.
|
* Kernel message header content.
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import {
|
|||||||
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
||||||
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||||
import { INotebookService, INotebookEditor } from 'sql/services/notebook/notebookService';
|
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
|
import { getProvidersForFileName } from 'sql/parts/notebook/notebookUtils';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { disposed } from 'vs/base/common/errors';
|
import { disposed } from 'vs/base/common/errors';
|
||||||
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
@@ -57,6 +57,10 @@ class MainThreadNotebookEditor extends Disposable {
|
|||||||
return this.editor.notebookParams.providerId;
|
return this.editor.notebookParams.providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get providers(): string[] {
|
||||||
|
return this.editor.notebookParams.providers;
|
||||||
|
}
|
||||||
|
|
||||||
public get cells(): ICellModel[] {
|
public get cells(): ICellModel[] {
|
||||||
return this.editor.cells;
|
return this.editor.cells;
|
||||||
}
|
}
|
||||||
@@ -316,11 +320,20 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
let trusted = uri.scheme === Schemas.untitled;
|
let trusted = uri.scheme === Schemas.untitled;
|
||||||
let model = new NotebookInputModel(uri, undefined, trusted, undefined);
|
let model = new NotebookInputModel(uri, undefined, trusted, undefined);
|
||||||
let providerId = options.providerId;
|
let providerId = options.providerId;
|
||||||
if (!providerId) {
|
let providers: string[] = undefined;
|
||||||
|
if (!providerId)
|
||||||
|
{
|
||||||
// Ensure there is always a sensible provider ID for this file type
|
// Ensure there is always a sensible provider ID for this file type
|
||||||
providerId = getProviderForFileName(uri.fsPath, this._notebookService);
|
providers = getProvidersForFileName(uri.fsPath, this._notebookService);
|
||||||
|
// Try to use a non-builtin provider first
|
||||||
|
if (providers) {
|
||||||
|
providerId = providers.find(p => p !== DEFAULT_NOTEBOOK_PROVIDER);
|
||||||
|
if (!providerId) {
|
||||||
|
providerId = model.providerId;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.providers = providers;
|
||||||
model.providerId = providerId;
|
model.providerId = providerId;
|
||||||
let input = this._instantiationService.createInstance(NotebookInput, undefined, model);
|
let input = this._instantiationService.createInstance(NotebookInput, undefined, model);
|
||||||
|
|
||||||
@@ -452,6 +465,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
uri: editor.uri,
|
uri: editor.uri,
|
||||||
isDirty: editor.isDirty,
|
isDirty: editor.isDirty,
|
||||||
providerId: editor.providerId,
|
providerId: editor.providerId,
|
||||||
|
providers: editor.providers,
|
||||||
cells: this.convertCellModelToNotebookCell(editor.cells)
|
cells: this.convertCellModelToNotebookCell(editor.cells)
|
||||||
};
|
};
|
||||||
return addData;
|
return addData;
|
||||||
@@ -463,6 +477,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
cells: this.convertCellModelToNotebookCell(editor.cells),
|
cells: this.convertCellModelToNotebookCell(editor.cells),
|
||||||
isDirty: e.isDirty,
|
isDirty: e.isDirty,
|
||||||
providerId: editor.providerId,
|
providerId: editor.providerId,
|
||||||
|
providers: editor.providers,
|
||||||
uri: editor.uri
|
uri: editor.uri
|
||||||
};
|
};
|
||||||
return changeData;
|
return changeData;
|
||||||
|
|||||||
@@ -796,6 +796,7 @@ export interface INotebookDocumentsAndEditorsDelta {
|
|||||||
export interface INotebookModelAddedData {
|
export interface INotebookModelAddedData {
|
||||||
uri: UriComponents;
|
uri: UriComponents;
|
||||||
providerId: string;
|
providerId: string;
|
||||||
|
providers: string[];
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
cells: sqlops.nb.NotebookCell[];
|
cells: sqlops.nb.NotebookCell[];
|
||||||
}
|
}
|
||||||
@@ -803,6 +804,7 @@ export interface INotebookModelAddedData {
|
|||||||
export interface INotebookModelChangedData {
|
export interface INotebookModelChangedData {
|
||||||
uri: UriComponents;
|
uri: UriComponents;
|
||||||
providerId: string;
|
providerId: string;
|
||||||
|
providers: string[];
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
cells: sqlops.nb.NotebookCell[];
|
cells: sqlops.nb.NotebookCell[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class NotebookModelStub implements INotebookModel {
|
|||||||
get clientSession(): IClientSession {
|
get clientSession(): IClientSession {
|
||||||
throw new Error('method not implemented.');
|
throw new Error('method not implemented.');
|
||||||
}
|
}
|
||||||
get notebookManager(): INotebookManager {
|
get notebookManagers(): INotebookManager[] {
|
||||||
throw new Error('method not implemented.');
|
throw new Error('method not implemented.');
|
||||||
}
|
}
|
||||||
get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||||
@@ -53,6 +53,9 @@ export class NotebookModelStub implements INotebookModel {
|
|||||||
get contexts(): IDefaultConnection {
|
get contexts(): IDefaultConnection {
|
||||||
throw new Error('method not implemented.');
|
throw new Error('method not implemented.');
|
||||||
}
|
}
|
||||||
|
get providerId(): string {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
changeKernel(displayName: string): void {
|
changeKernel(displayName: string): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ let mockModelFactory: TypeMoq.Mock<ModelFactory>;
|
|||||||
let notificationService: TypeMoq.Mock<INotificationService>;
|
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||||
|
|
||||||
describe('notebook model', function(): void {
|
describe('notebook model', function(): void {
|
||||||
let notebookManager = new NotebookManagerStub();
|
let notebookManagers = [new NotebookManagerStub()];
|
||||||
let memento: TypeMoq.Mock<Memento>;
|
let memento: TypeMoq.Mock<Memento>;
|
||||||
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
|
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
|
||||||
let defaultModelOptions: INotebookModelOptions;
|
let defaultModelOptions: INotebookModelOptions;
|
||||||
@@ -87,9 +87,10 @@ describe('notebook model', function(): void {
|
|||||||
defaultModelOptions = {
|
defaultModelOptions = {
|
||||||
notebookUri: defaultUri,
|
notebookUri: defaultUri,
|
||||||
factory: new ModelFactory(),
|
factory: new ModelFactory(),
|
||||||
notebookManager,
|
notebookManagers,
|
||||||
notificationService: notificationService.object,
|
notificationService: notificationService.object,
|
||||||
connectionService: queryConnectionService.object };
|
connectionService: queryConnectionService.object,
|
||||||
|
providerId: 'jupyter' };
|
||||||
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
|
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
|
||||||
mockClientSession.setup(c => c.initialize(TypeMoq.It.isAny())).returns(() => {
|
mockClientSession.setup(c => c.initialize(TypeMoq.It.isAny())).returns(() => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -118,7 +119,7 @@ describe('notebook model', function(): void {
|
|||||||
|
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
|
||||||
notebookManager.contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// When I initialize the model
|
// When I initialize the model
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
@@ -134,7 +135,7 @@ describe('notebook model', function(): void {
|
|||||||
let error = new Error('File not found');
|
let error = new Error('File not found');
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
|
||||||
notebookManager.contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// When I initalize the model
|
// When I initalize the model
|
||||||
// Then it should throw
|
// Then it should throw
|
||||||
@@ -148,7 +149,7 @@ describe('notebook model', function(): void {
|
|||||||
// Given a notebook with 2 cells
|
// Given a notebook with 2 cells
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
|
||||||
notebookManager.contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// When I initalize the model
|
// When I initalize the model
|
||||||
let model = new NotebookModel(defaultModelOptions);
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
@@ -163,7 +164,7 @@ describe('notebook model', function(): void {
|
|||||||
it('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
|
it('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||||
notebookManager.contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
|
|
||||||
// Given I have a session that fails to start
|
// Given I have a session that fails to start
|
||||||
mockClientSession.setup(c => c.isInErrorState).returns(() => true);
|
mockClientSession.setup(c => c.isInErrorState).returns(() => true);
|
||||||
@@ -192,7 +193,7 @@ describe('notebook model', function(): void {
|
|||||||
it('Should not be in error state if client session initialization succeeds', async function(): Promise<void> {
|
it('Should not be in error state if client session initialization succeeds', async function(): Promise<void> {
|
||||||
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||||
notebookManager.contentManager = mockContentManager.object;
|
notebookManagers[0].contentManager = mockContentManager.object;
|
||||||
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
|
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
|
||||||
|
|
||||||
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
|
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ suite('ExtHostNotebook Tests', () => {
|
|||||||
|
|
||||||
class NotebookProviderStub implements sqlops.nb.NotebookProvider {
|
class NotebookProviderStub implements sqlops.nb.NotebookProvider {
|
||||||
providerId: string = 'TestProvider';
|
providerId: string = 'TestProvider';
|
||||||
|
standardKernels: string[] = ['fakeKernel'];
|
||||||
|
|
||||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<sqlops.nb.NotebookManager> {
|
getNotebookManager(notebookUri: vscode.Uri): Thenable<sqlops.nb.NotebookManager> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
|
|||||||
Reference in New Issue
Block a user