SQL Kernel Improvements/Removing Spark Code from Core/Attach to Changes (#3790)

* Scenarios work besides loading saved kernel

* Fix compilation issue

* Save and load functional

* Fix loading kernesl issue when sql kernel is not enabled

* Fix language mapping to not be hardcoded any longer

* Remove unnecessary comment

* PR Comments vol. 1

* Code cleanup, use ConnectionProfile instead of IConnectionProfile when accessing serverName

* PR changes vol. 2

* One final comment for PR

* Fix linting issue
This commit is contained in:
Chris LaFreniere
2019-01-25 18:54:04 -08:00
committed by GitHub
parent ea67859de7
commit 43be88a37c
29 changed files with 768 additions and 627 deletions

View File

@@ -15,7 +15,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/workbench/services/notebook/common/notebookService'; import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
import { getProvidersForFileName } from 'sql/parts/notebook/notebookUtils'; import { getProvidersForFileName, getStandardKernelsForProvider } 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');
@@ -69,6 +69,10 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined); let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
notebookInputModel.providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0]; notebookInputModel.providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0];
notebookInputModel.providers = providerIds; notebookInputModel.providers = providerIds;
notebookInputModel.providers.forEach(provider => {
let standardKernels = getStandardKernelsForProvider(provider, notebookService);
notebookInputModel.standardKernels = standardKernels;
});
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel); let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
return notebookInput; return notebookInput;
}); });

View File

@@ -20,8 +20,6 @@ let modelId = 0;
export class CellModel implements ICellModel { export class CellModel implements ICellModel {
private static LanguageMapping: Map<string, string>;
private _cellType: nb.CellType; private _cellType: nb.CellType;
private _source: string; private _source: string;
private _language: string; private _language: string;
@@ -37,7 +35,6 @@ export class CellModel implements ICellModel {
constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) { constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) {
this.id = `${modelId++}`; this.id = `${modelId++}`;
CellModel.CreateLanguageMappings();
if (cellData) { if (cellData) {
// Read in contents if available // Read in contents if available
this.fromJSON(cellData); this.fromJSON(cellData);
@@ -238,9 +235,9 @@ export class CellModel implements ICellModel {
try { try {
let result = output as nb.IDisplayResult; let result = output as nb.IDisplayResult;
if (result && result.data && result.data['text/html']) { if (result && result.data && result.data['text/html']) {
let nbm = (this as CellModel).options.notebook as NotebookModel; let model = (this as CellModel).options.notebook as NotebookModel;
if (nbm.hadoopConnection) { if (model.activeConnection) {
let host = nbm.hadoopConnection.host; let host = model.activeConnection.serverName;
let html = result.data['text/html']; let html = result.data['text/html'];
html = html.replace(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, function (a, b, c) { html = html.replace(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, function (a, b, c) {
let ret = ''; let ret = '';
@@ -321,18 +318,6 @@ export class CellModel implements ICellModel {
} }
} }
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';
CellModel.LanguageMapping['sql'] = 'sql';
}
private get languageInfo(): nb.ILanguageInfo { private get languageInfo(): nb.ILanguageInfo {
if (this._options && this._options.notebook && this._options.notebook.languageInfo) { if (this._options && this._options.notebook && this._options.notebook.languageInfo) {
return this._options.notebook.languageInfo; return this._options.notebook.languageInfo;
@@ -371,17 +356,15 @@ export class CellModel implements ICellModel {
// Otherwise, default to python as the language // Otherwise, default to python as the language
let languageInfo = this.languageInfo; let languageInfo = this.languageInfo;
if (languageInfo) { if (languageInfo) {
if (languageInfo.name) { if (languageInfo.codemirror_mode) {
// check the LanguageMapping to determine if a mapping is necessary (example 'pyspark' -> 'python') let codeMirrorMode: nb.ICodeMirrorMode = <nb.ICodeMirrorMode>(languageInfo.codemirror_mode);
if (CellModel.LanguageMapping[languageInfo.name]) { if (codeMirrorMode && codeMirrorMode.name) {
this._language = CellModel.LanguageMapping[languageInfo.name]; this._language = codeMirrorMode.name;
} }
else { } else if (languageInfo.mimetype) {
this._language = languageInfo.name;
}
}
else if (languageInfo.mimetype) {
this._language = languageInfo.mimetype; this._language = languageInfo.mimetype;
} else if (languageInfo.name) {
this._language = languageInfo.name;
} }
} }

View File

@@ -17,10 +17,8 @@ import { IClientSession, IKernelPreference, IClientSessionOptions } from './mode
import { Deferred } from 'sql/base/common/promise'; import { Deferred } from 'sql/base/common/promise';
import * as notebookUtils from '../notebookUtils'; import * as notebookUtils from '../notebookUtils';
import * as sparkUtils from '../spark/sparkUtils';
import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
/** /**
* Implementation of a client session. This is a model over session operations, * Implementation of a client session. This is a model over session operations,
@@ -49,7 +47,6 @@ export class ClientSession implements IClientSession {
private _session: nb.ISession; private _session: nb.ISession;
private isServerStarted: boolean; private isServerStarted: boolean;
private notebookManager: INotebookManager; private notebookManager: INotebookManager;
private _connection: NotebookConnection;
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = []; private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
constructor(private options: IClientSessionOptions) { constructor(private options: IClientSessionOptions) {
@@ -60,10 +57,8 @@ export class ClientSession implements IClientSession {
this._kernelChangeCompleted = new Deferred<void>(); this._kernelChangeCompleted = new Deferred<void>();
} }
public async initialize(connection?: NotebookConnection): Promise<void> { public async initialize(): Promise<void> {
try { try {
this._kernelConfigActions.push((kernelName: string) => { return this.runTasksBeforeSessionStart(kernelName); });
this._connection = connection;
this._serverLoadFinished = this.startServer(); this._serverLoadFinished = this.startServer();
await this._serverLoadFinished; await this._serverLoadFinished;
await this.initializeSession(); await this.initializeSession();
@@ -114,7 +109,7 @@ export class ClientSession implements IClientSession {
} catch (err) { } catch (err) {
// TODO move registration // TODO move registration
if (err && err.response && err.response.status === 501) { 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)); this.options.notificationService.warn(nls.localize('kernelRequiresConnection', 'Kernel {0} was not found. The default kernel will be used instead.', kernelName));
session = await this.notebookManager.sessionManager.startNew({ session = await this.notebookManager.sessionManager.startNew({
path: this.notebookUri.fsPath, path: this.notebookUri.fsPath,
kernelName: undefined kernelName: undefined
@@ -256,41 +251,22 @@ export class ClientSession implements IClientSession {
return kernel; return kernel;
} }
public async runTasksBeforeSessionStart(kernelName: string): Promise<void> { public async configureKernel(options: nb.IKernelSpec): Promise<void> {
// TODO we should move all Spark-related code to SparkMagicContext if (this._session) {
if (this._session && this._connection && this.isSparkKernel(kernelName)) { await this._session.configureKernel(options);
// 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> { public async updateConnection(connection: IConnectionProfile): Promise<void> {
if (!this.kernel) { if (!this.kernel) {
// TODO is there any case where skipping causes errors? So far it seems like it gets called twice // TODO is there any case where skipping causes errors? So far it seems like it gets called twice
return; return;
} }
this._connection = (connection.connectionProfile.id !== '-1') ? connection : this._connection; if (connection.id !== '-1') {
// if kernel is not set, don't run kernel config actions await this._session.configureConnection(connection);
// 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. * Kill the kernel and shutdown the session.
* *

View File

@@ -17,9 +17,11 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts'; import { CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IStandardKernelWithProvider } from 'sql/parts/notebook/notebookUtils';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
export interface IClientSessionOptions { export interface IClientSessionOptions {
notebookUri: URI; notebookUri: URI;
@@ -131,7 +133,7 @@ export interface IClientSession extends IDisposable {
* This will optionally start a session if the kernel preferences * This will optionally start a session if the kernel preferences
* indicate this is desired * indicate this is desired
*/ */
initialize(connection?: NotebookConnection): Promise<void>; initialize(connection?: IConnectionProfile): Promise<void>;
/** /**
* Change the current kernel associated with the document. * Change the current kernel associated with the document.
@@ -140,6 +142,13 @@ export interface IClientSession extends IDisposable {
options: nb.IKernelSpec options: nb.IKernelSpec
): Promise<nb.IKernel>; ): Promise<nb.IKernel>;
/**
* Configure the current kernel associated with the document.
*/
configureKernel(
options: nb.IKernelSpec
): Promise<void>;
/** /**
* Kill the kernel and shutdown the session. * Kill the kernel and shutdown the session.
* *
@@ -191,12 +200,12 @@ export interface IClientSession extends IDisposable {
/** /**
* Updates the connection * Updates the connection
*/ */
updateConnection(connection: NotebookConnection): void; updateConnection(connection: IConnectionProfile): void;
} }
export interface IDefaultConnection { export interface IDefaultConnection {
defaultConnection: IConnectionProfile; defaultConnection: ConnectionProfile;
otherConnections: IConnectionProfile[]; otherConnections: ConnectionProfile[];
} }
/** /**
@@ -348,6 +357,8 @@ export interface INotebookModel {
* @param edits The edit operations to perform * @param edits The edit operations to perform
*/ */
pushEditOperations(edits: ISingleNotebookEditOperation[]): void; pushEditOperations(edits: ISingleNotebookEditOperation[]): void;
getApplicableConnectionProviderIds(kernelName: string): string[];
} }
export interface NotebookContentChange { export interface NotebookContentChange {
@@ -418,16 +429,14 @@ export interface INotebookModelOptions {
notebookManagers: INotebookManager[]; notebookManagers: INotebookManager[];
providerId: string; providerId: string;
standardKernels: IStandardKernelWithProvider[];
defaultKernel: nb.IKernelSpec;
notificationService: INotificationService; notificationService: INotificationService;
connectionService: IConnectionManagementService; connectionService: IConnectionManagementService;
capabilitiesService: ICapabilitiesService;
} }
// TODO would like to move most of these constants to an extension
export namespace notebookConstants { export namespace notebookConstants {
export const hadoopKnoxProviderName = 'HADOOP_KNOX'; export const SQL = 'SQL';
export const python3 = 'python3';
export const python3DisplayName = 'Python 3';
export const defaultSparkKernel = 'pyspark3kernel';
export const hostPropName = 'host';
} }

View File

@@ -11,7 +11,6 @@ import { localize } from 'vs/nls';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export namespace constants { export namespace constants {
export const hostPropName = 'host';
export const userPropName = 'user'; export const userPropName = 'user';
export const knoxPortPropName = 'knoxport'; export const knoxPortPropName = 'knoxport';
export const clusterPropName = 'clustername'; export const clusterPropName = 'clustername';
@@ -52,7 +51,7 @@ export class NotebookConnection {
* preference to the built in port. * preference to the built in port.
*/ */
private ensureHostAndPort(): void { private ensureHostAndPort(): void {
this._host = this.connectionProfile.options[constants.hostPropName]; this._host = this.connectionProfile.serverName;
this._knoxPort = NotebookConnection.getKnoxPortOrDefault(this.connectionProfile); this._knoxPort = NotebookConnection.getKnoxPortOrDefault(this.connectionProfile);
// determine whether the host has either a ',' or ':' in it // determine whether the host has either a ',' or ':' in it
this.setHostAndPort(','); this.setHostAndPort(',');

View File

@@ -0,0 +1,152 @@
/*---------------------------------------------------------------------------------------------
* 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 { IDefaultConnection, notebookConstants, INotebookModelOptions } from 'sql/parts/notebook/models/modelInterfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export class NotebookContexts {
private static MSSQL_PROVIDER = 'MSSQL';
private static get DefaultContext(): IDefaultConnection {
let defaultConnection: ConnectionProfile = <any>{
providerName: NotebookContexts.MSSQL_PROVIDER,
id: '-1',
serverName: localize('selectConnection', 'Select connection')
};
return {
// default context if no other contexts are applicable
defaultConnection: defaultConnection,
otherConnections: [defaultConnection]
};
}
private static get LocalContext(): IDefaultConnection {
let localConnection: ConnectionProfile = <any>{
providerName: NotebookContexts.MSSQL_PROVIDER,
id: '-1',
serverName: localize('localhost', 'localhost')
};
return {
// default context if no other contexts are applicable
defaultConnection: localConnection,
otherConnections: [localConnection]
};
}
/**
* Get all of the applicable contexts for a given kernel
* @param connectionService connection management service
* @param connProviderIds array of connection provider ids applicable for a kernel
* @param kernelChangedArgs kernel changed args (both old and new kernel info)
* @param profile current connection profile
*/
public static async getContextsForKernel(connectionService: IConnectionManagementService, connProviderIds: string[], 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 && connProviderIds.length < 1) {
return connections;
} else {
connections = await this.getActiveContexts(connectionService, connProviderIds, profile);
}
return connections;
}
/**
* Get all active contexts and sort them
* @param apiWrapper ApiWrapper
* @param profile current connection profile
*/
public static async getActiveContexts(connectionService: IConnectionManagementService, connProviderIds: string[], profile: IConnectionProfile): Promise<IDefaultConnection> {
let defaultConnection: ConnectionProfile = NotebookContexts.DefaultContext.defaultConnection;
let activeConnections: ConnectionProfile[] = await connectionService.getActiveConnections();
if (activeConnections && activeConnections.length > 0) {
activeConnections = activeConnections.filter(conn => conn.id !== '-1');
}
// If no connection provider ids exist for a given kernel, the attach to should show localhost
if (connProviderIds.length === 0) {
return NotebookContexts.LocalContext;
}
// If no active connections exist, show "Select connection" as the default value
if (activeConnections.length === 0) {
return NotebookContexts.DefaultContext;
}
// Filter active connections by their provider ids to match kernel's supported connection providers
else if (activeConnections.length > 0) {
let connections = activeConnections.filter(connection => {
return connProviderIds.includes(connection.providerName);
});
if (connections && connections.length > 0) {
defaultConnection = connections[0];
if (profile && profile.options) {
if (connections.find(connection => connection.serverName === profile.serverName)) {
defaultConnection = connections.find(connection => connection.serverName === profile.serverName);
}
}
}
activeConnections = [];
connections.forEach(connection => activeConnections.push(connection));
}
if (defaultConnection === NotebookContexts.DefaultContext.defaultConnection) {
let newConnection = <ConnectionProfile><any>{
providerName: 'SQL',
id: '-2',
serverName: localize('addConnection', 'Add new connection')
};
activeConnections.push(newConnection);
}
return {
otherConnections: activeConnections,
defaultConnection: defaultConnection
};
}
/**
*
* @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): nb.IKernelSpec {
let defaultKernel: nb.IKernelSpec;
if (specs) {
// find the saved kernel (if it exists)
if (savedKernelInfo) {
defaultKernel = specs.kernels.find((kernel) => kernel.name === savedKernelInfo.name);
}
// if no saved kernel exists, use the default KernelSpec
if (!defaultKernel) {
defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
}
if (defaultKernel) {
return defaultKernel;
}
}
// If no default kernel specified (should never happen), default to SQL
if (!defaultKernel) {
defaultKernel = {
name: notebookConstants.SQL,
display_name: notebookConstants.SQL
};
}
return defaultKernel;
}
}

View File

@@ -17,13 +17,13 @@ import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contract
import { nbversion } from '../notebookConstants'; import { nbversion } from '../notebookConstants';
import * as notebookUtils from '../notebookUtils'; import * as notebookUtils from '../notebookUtils';
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
import { SparkMagicContexts } from 'sql/parts/notebook/models/sparkMagicContexts'; import { NotebookContexts } from 'sql/parts/notebook/models/notebookContexts';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { INotification, Severity } from 'vs/platform/notification/common/notification'; import { INotification, Severity } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
/* /*
* Used to control whether a message in a dialog/wizard is displayed as an error, * Used to control whether a message in a dialog/wizard is displayed as an error,
@@ -58,11 +58,12 @@ export class NotebookModel extends Disposable implements INotebookModel {
private _savedKernelInfo: nb.IKernelInfo; private _savedKernelInfo: nb.IKernelInfo;
private readonly _nbformat: number = nbversion.MAJOR_VERSION; private readonly _nbformat: number = nbversion.MAJOR_VERSION;
private readonly _nbformatMinor: number = nbversion.MINOR_VERSION; private readonly _nbformatMinor: number = nbversion.MINOR_VERSION;
private _hadoopConnection: NotebookConnection; private _activeConnection: ConnectionProfile;
private _defaultKernel: nb.IKernelSpec;
private _activeCell: ICellModel; private _activeCell: ICellModel;
private _providerId: string; private _providerId: string;
private _isNewNotebook: boolean = true; private _defaultKernel: nb.IKernelSpec;
private _kernelDisplayNameToConnectionProviderIds: Map<string, string[]> = new Map<string, string[]>();
private _kernelDisplayNameToNotebookProviderIds: Map<string, string> = new Map<string, string>();
constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) { constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) {
super(); super();
@@ -74,6 +75,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
this._trustedMode = false; this._trustedMode = false;
this._providerId = notebookOptions.providerId; this._providerId = notebookOptions.providerId;
this.notebookOptions.standardKernels.forEach(kernel => {
this._kernelDisplayNameToConnectionProviderIds.set(kernel.name, kernel.connectionProviderIds);
this._kernelDisplayNameToNotebookProviderIds.set(kernel.name, kernel.notebookProvider);
});
this._defaultKernel = notebookOptions.defaultKernel;
} }
public get notebookManagers(): INotebookManager[] { public get notebookManagers(): INotebookManager[] {
@@ -171,10 +177,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
return this._trustedMode; return this._trustedMode;
} }
public get isNewNotebook(): boolean {
return this._isNewNotebook;
}
public get providerId(): string { public get providerId(): string {
return this._providerId; return this._providerId;
} }
@@ -188,8 +190,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
} }
public get hadoopConnection(): NotebookConnection { public get activeConnection(): IConnectionProfile {
return this._hadoopConnection; return this._activeConnection;
} }
/** /**
@@ -207,6 +209,14 @@ export class NotebookModel extends Disposable implements INotebookModel {
return this._onClientSessionReady.event; return this._onClientSessionReady.event;
} }
public getApplicableConnectionProviderIds(kernelDisplayName: string): string[] {
let ids = [];
if (kernelDisplayName) {
ids = this._kernelDisplayNameToConnectionProviderIds.get(kernelDisplayName);
}
return !ids ? [] : ids;
}
public async requestModelLoad(isTrusted: boolean = false): Promise<void> { public async requestModelLoad(isTrusted: boolean = false): Promise<void> {
try { try {
this._trustedMode = isTrusted; this._trustedMode = isTrusted;
@@ -223,9 +233,12 @@ export class NotebookModel extends Disposable implements INotebookModel {
version: '' version: ''
}; };
if (contents) { if (contents) {
this._isNewNotebook = false;
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents); this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
this._savedKernelInfo = this.getSavedKernelInfo(contents); this._savedKernelInfo = this.getSavedKernelInfo(contents);
this.setProviderIdForKernel(this._savedKernelInfo);
if (this._savedKernelInfo) {
this._defaultKernel = this._savedKernelInfo;
}
if (contents.cells && contents.cells.length > 0) { if (contents.cells && contents.cells.length > 0) {
this._cells = contents.cells.map(c => factory.createCell(c, { notebook: this, isTrusted: isTrusted })); this._cells = contents.cells.map(c => factory.createCell(c, { notebook: this, isTrusted: isTrusted }));
} }
@@ -338,15 +351,15 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (!this._activeClientSession) { if (!this._activeClientSession) {
this._activeClientSession = clientSession; this._activeClientSession = clientSession;
} }
let profile = this.connectionProfile as IConnectionProfile; let profile = new ConnectionProfile(this.notebookOptions.capabilitiesService, this.connectionProfile);
if (this.isValidKnoxConnection(profile)) { if (this.isValidConnection(profile)) {
this._hadoopConnection = new NotebookConnection(this.connectionProfile); this._activeConnection = profile;
} else { } else {
this._hadoopConnection = undefined; this._activeConnection = undefined;
} }
clientSession.initialize(this._hadoopConnection); clientSession.initialize(this._activeConnection);
this._sessionLoadFinished = clientSession.ready.then(async () => { this._sessionLoadFinished = clientSession.ready.then(async () => {
if (clientSession.isInErrorState) { if (clientSession.isInErrorState) {
this.setErrorState(clientSession.errorMessage); this.setErrorState(clientSession.errorMessage);
@@ -360,8 +373,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
}); });
} }
private isValidKnoxConnection(profile: IConnectionProfile | connection.Connection) { private isValidConnection(profile: IConnectionProfile | connection.Connection) {
return profile && profile.providerName === notebookConstants.hadoopKnoxProviderName && profile.options[notebookConstants.hostPropName] !== undefined; let standardKernels = this.notebookOptions.standardKernels.find(kernel => this._savedKernelInfo && kernel.name === this._savedKernelInfo.display_name);
let connectionProviderIds = standardKernels ? standardKernels.connectionProviderIds : undefined;
return profile && connectionProviderIds && connectionProviderIds.find(provider => provider === profile.providerName) !== undefined;
} }
public get languageInfo(): nb.ILanguageInfo { public get languageInfo(): nb.ILanguageInfo {
@@ -380,45 +395,49 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> { public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> {
this.findProviderIdForKernel(kernelSpec); this.setProviderIdForKernel(kernelSpec);
if (this._activeClientSession && this._activeClientSession.isReady) {
return this._activeClientSession.changeKernel(kernelSpec) return this._activeClientSession.changeKernel(kernelSpec)
.then((kernel) => { .then((kernel) => {
this.updateKernelInfo(kernel);
kernel.ready.then(() => { kernel.ready.then(() => {
if (kernel.info) { if (kernel.info) {
this.updateLanguageInfo(kernel.info.language_info); this.updateLanguageInfo(kernel.info.language_info);
} }
}, err => undefined); }, err => undefined);
return this.updateKernelInfo(kernel);
}).catch((err) => { }).catch((err) => {
this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err))); this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err)));
// TODO should revert kernels dropdown // TODO should revert kernels dropdown
}); });
} }
this.notifyError(localize('noActiveClientSessionFound', 'No active client session was found.'));
return Promise.resolve();
}
public changeContext(host: string, newConnection?: IConnectionProfile): void { public changeContext(server: string, newConnection?: IConnectionProfile): void {
try { try {
if (!newConnection) { if (!newConnection) {
newConnection = this._activeContexts.otherConnections.find((connection) => connection.options['host'] === host); newConnection = this._activeContexts.otherConnections.find((connection) => connection.serverName === server);
} }
if (!newConnection && this._activeContexts.defaultConnection.options['host'] === host) { if (!newConnection && (this._activeContexts.defaultConnection.serverName === server)) {
newConnection = this._activeContexts.defaultConnection; newConnection = this._activeContexts.defaultConnection;
} }
SparkMagicContexts.configureContext(); let newConnectionProfile = new ConnectionProfile(this.notebookOptions.capabilitiesService, newConnection);
this._hadoopConnection = new NotebookConnection(newConnection); this._activeConnection = newConnectionProfile;
this.refreshConnections(newConnection); this.refreshConnections(newConnectionProfile);
this._activeClientSession.updateConnection(this._hadoopConnection); this._activeClientSession.updateConnection(this._activeConnection);
} 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));
} }
} }
private refreshConnections(newConnection: IConnectionProfile) { private refreshConnections(newConnection: ConnectionProfile) {
if (this.isValidKnoxConnection(newConnection) && if (this.isValidConnection(newConnection) &&
this._hadoopConnection.connectionProfile.id !== '-1' && this._activeConnection.id !== '-1' &&
this._hadoopConnection.connectionProfile.id !== this._activeContexts.defaultConnection.id) { this._activeConnection.id !== this._activeContexts.defaultConnection.id) {
// Put the defaultConnection to the head of otherConnections // Put the defaultConnection to the head of otherConnections
if (this.isValidKnoxConnection(this._activeContexts.defaultConnection)) { if (this.isValidConnection(this._activeContexts.defaultConnection)) {
this._activeContexts.otherConnections = this._activeContexts.otherConnections.filter(conn => conn.id !== this._activeContexts.defaultConnection.id); this._activeContexts.otherConnections = this._activeContexts.otherConnections.filter(conn => conn.id !== this._activeContexts.defaultConnection.id);
this._activeContexts.otherConnections.unshift(this._activeContexts.defaultConnection); this._activeContexts.otherConnections.unshift(this._activeContexts.defaultConnection);
} }
@@ -439,18 +458,15 @@ export class NotebookModel extends Disposable implements INotebookModel {
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); if (!this._defaultKernel) {
this._defaultKernel = defaultKernel; this._defaultKernel = NotebookContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo);
}
this._clientSessions.forEach(clientSession => { this._clientSessions.forEach(clientSession => {
clientSession.statusChanged(async (session) => { clientSession.statusChanged(async (session) => {
if (session && session.defaultKernelLoaded === true) { this._kernelsChangedEmitter.fire(session.kernel);
this._kernelsChangedEmitter.fire(defaultKernel);
} else if (session && !session.defaultKernelLoaded) {
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
}
}); });
}); });
this.doChangeKernel(defaultKernel); this.doChangeKernel(this._defaultKernel);
} }
} catch (err) { } catch (err) {
let msg = notebookUtils.getErrorMessage(err); let msg = notebookUtils.getErrorMessage(err);
@@ -484,6 +500,15 @@ export class NotebookModel extends Disposable implements INotebookModel {
return kernel; return kernel;
} }
private getDisplayNameFromSpecName(kernelid: string): string {
let newKernel = this.notebookManager.sessionManager.specs.kernels.find(kernel => kernel.name === kernelid);
let newKernelDisplayName;
if (newKernel) {
newKernelDisplayName = newKernel.display_name;
}
return newKernelDisplayName;
}
private setErrorState(errMsg: string): void { private setErrorState(errMsg: string): void {
this._inErrorState = true; this._inErrorState = true;
let msg = localize('startSessionFailed', 'Could not start session: {0}', errMsg); let msg = localize('startSessionFailed', 'Could not start session: {0}', errMsg);
@@ -499,6 +524,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
public async handleClosed(): Promise<void> { public async handleClosed(): Promise<void> {
try { try {
if (this._activeClientSession) { if (this._activeClientSession) {
try {
await this._activeClientSession.ready;
} catch(err) {
this.notifyError(localize('shutdownClientSessionError', 'A client session error occurred when closing the notebook: {0}', err));
}
await this._activeClientSession.shutdown(); await this._activeClientSession.shutdown();
this._clientSessions = undefined; this._clientSessions = undefined;
this._activeClientSession = undefined; this._activeClientSession = undefined;
@@ -509,11 +539,13 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
private async loadActiveContexts(kernelChangedArgs: nb.IKernelChangedArgs): Promise<void> { private async loadActiveContexts(kernelChangedArgs: nb.IKernelChangedArgs): Promise<void> {
this._activeContexts = await SparkMagicContexts.getContextsForKernel(this.notebookOptions.connectionService, kernelChangedArgs, this.connectionProfile); if (kernelChangedArgs && kernelChangedArgs.newValue && kernelChangedArgs.newValue.name) {
let kernelDisplayName = this.getDisplayNameFromSpecName(kernelChangedArgs.newValue.name);
this._activeContexts = await NotebookContexts.getContextsForKernel(this.notebookOptions.connectionService, this.getApplicableConnectionProviderIds(kernelDisplayName), kernelChangedArgs, this.connectionProfile);
this._contextsChangedEmitter.fire(); this._contextsChangedEmitter.fire();
if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.options !== undefined) { if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.serverName !== undefined) {
let defaultHadoopConnection = new NotebookConnection(this.contexts.defaultConnection); this.changeContext(this.contexts.defaultConnection.serverName);
this.changeContext(defaultHadoopConnection.host); }
} }
} }
@@ -555,6 +587,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
display_name: spec.display_name, display_name: spec.display_name,
language: spec.language language: spec.language
}; };
this.clientSession.configureKernel(this._savedKernelInfo);
} catch (err) { } catch (err) {
// Don't worry about this for now. Just use saved values // Don't worry about this for now. Just use saved values
} }
@@ -565,17 +598,27 @@ export class NotebookModel extends Disposable implements INotebookModel {
* Set _providerId and _activeClientSession based on a kernelSpec representing new kernel * Set _providerId and _activeClientSession based on a kernelSpec representing new kernel
* @param kernelSpec KernelSpec for new kernel * @param kernelSpec KernelSpec for new kernel
*/ */
private findProviderIdForKernel(kernelSpec: nb.IKernelSpec): void { private setProviderIdForKernel(kernelSpec: nb.IKernelSpec): void {
let sessionManagerFound: boolean = false;
for (let i = 0; i < this.notebookManagers.length; i++) { 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) { 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); let index = this.notebookManagers[i].sessionManager.specs.kernels.findIndex(kernel => kernel.name === kernelSpec.name);
if (index >= 0) { if (index >= 0) {
this._activeClientSession = this._clientSessions[i]; this._activeClientSession = this._clientSessions[i];
this._providerId = this.notebookManagers[i].providerId; this._providerId = this.notebookManagers[i].providerId;
sessionManagerFound = true;
break; break;
} }
} }
} }
// If no SessionManager exists, utilize passed in StandardKernels to see if we can intelligently set _providerId
if (!sessionManagerFound) {
let provider = this._kernelDisplayNameToNotebookProviderIds.get(kernelSpec.display_name);
if (provider) {
this._providerId = provider;
}
}
} }
/** /**
* Serialize the model to JSON. * Serialize the model to JSON.

View File

@@ -1,238 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IConnectionProfile } from 'sql/platform/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/platform/connection/common/connectionManagement';
const configBase = {
'kernel_python_credentials': {
'url': ''
},
'kernel_scala_credentials': {
'url': ''
},
'kernel_r_credentials': {
'url': ''
},
'ignore_ssl_errors': true,
'logging_config': {
'version': 1,
'formatters': {
'magicsFormatter': {
'format': '%(asctime)s\t%(levelname)s\t%(message)s',
'datefmt': ''
}
},
'handlers': {
'magicsHandler': {
'class': 'hdijupyterutils.filehandler.MagicsFileHandler',
'formatter': 'magicsFormatter',
'home_path': ''
}
},
'loggers': {
'magicsLogger': {
'handlers': ['magicsHandler'],
'level': 'DEBUG',
'propagate': 0
}
}
}
};
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 && activeConnections.length > 0) {
// Remove all non-Spark connections
activeConnections = activeConnections.filter(conn => conn.providerName === notebookConstants.hadoopKnoxProviderName);
}
if (activeConnections.length === 0) {
return SparkMagicContexts.DefaultContext;
}
// 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(): 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);
// Default to localhost in config file.
let creds: ICredentials = {
'url': 'http://localhost:8088'
};
let config: ISparkMagicConfig = Object.assign({}, configBase);
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 foundSavedKernelInSpecs;
let defaultKernel;
if (specs) {
defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
if (savedKernelInfo) {
foundSavedKernelInSpecs = specs.kernels.find((kernel) => kernel.name === savedKernelInfo.name);
}
}
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 = !foundSavedKernelInSpecs ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : foundSavedKernelInSpecs;
} 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 {1} will be used instead.', savedKernelInfo.display_name, defaultKernel.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;
ignore_ssl_errors?: boolean;
logging_config: {
handlers: {
magicsHandler: {
home_path: string;
class?: string;
formatter?: string
}
}
};
}
export interface IKernelJupyterID {
id: string;
jupyterId: string;
}

View File

@@ -49,6 +49,7 @@ import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHos
import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
export const NOTEBOOK_SELECTOR: string = 'notebook-component'; export const NOTEBOOK_SELECTOR: string = 'notebook-component';
@@ -92,7 +93,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
@Inject(IWindowService) private windowService: IWindowService, @Inject(IWindowService) private windowService: IWindowService,
@Inject(IViewletService) private viewletService: IViewletService, @Inject(IViewletService) private viewletService: IViewletService,
@Inject(IUntitledEditorService) private untitledEditorService: IUntitledEditorService, @Inject(IUntitledEditorService) private untitledEditorService: IUntitledEditorService,
@Inject(IEditorGroupsService) private editorGroupService: IEditorGroupsService @Inject(IEditorGroupsService) private editorGroupService: IEditorGroupsService,
@Inject(ICapabilitiesService) private capabilitiesService: ICapabilitiesService
) { ) {
super(); super();
this.updateProfile(); this.updateProfile();
@@ -105,11 +107,11 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
// use global connection if possible // use global connection if possible
let profile = TaskUtilities.getCurrentGlobalConnection(this.objectExplorerService, this.connectionManagementService, this.editorService); let profile = TaskUtilities.getCurrentGlobalConnection(this.objectExplorerService, this.connectionManagementService, this.editorService);
// TODO use generic method to match kernel with valid connection that's compatible. For now, we only have 1 // TODO use generic method to match kernel with valid connection that's compatible. For now, we only have 1
if (profile && profile.providerName === notebookConstants.hadoopKnoxProviderName) { if (profile && profile.providerName) {
this.profile = profile; this.profile = profile;
} else { } else {
// if not, try 1st active connection that matches our filter // if not, try 1st active connection that matches our filter
let profiles = this.connectionManagementService.getActiveConnections([notebookConstants.hadoopKnoxProviderName]); let profiles = this.connectionManagementService.getActiveConnections();
if (profiles && profiles.length > 0) { if (profiles && profiles.length > 0) {
this.profile = profiles[0]; this.profile = profiles[0];
} }
@@ -239,7 +241,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
connectionService: this.connectionManagementService, connectionService: this.connectionManagementService,
notificationService: this.notificationService, notificationService: this.notificationService,
notebookManagers: this.notebookManagers, notebookManagers: this.notebookManagers,
providerId: notebookUtils.sqlNotebooksEnabled() ? 'sql' : 'jupyter' // this is tricky; really should also depend on the connection profile standardKernels: this._notebookParams.input.standardKernels,
providerId: notebookUtils.sqlNotebooksEnabled() ? 'sql' : 'jupyter', // this is tricky; really should also depend on the connection profile
defaultKernel: this._notebookParams.input.defaultKernel,
capabilitiesService: this.capabilitiesService
}, 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);
@@ -324,7 +329,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
let attachToContainer = document.createElement('div'); let attachToContainer = document.createElement('div');
let attachTodropdwon = new AttachToDropdown(attachToContainer, this.contextViewService, this.modelRegistered, let attachTodropdwon = new AttachToDropdown(attachToContainer, this.contextViewService, this.modelRegistered,
this.connectionManagementService, this.connectionDialogService, this.notificationService); this.connectionManagementService, this.connectionDialogService, this.notificationService, this.capabilitiesService);
attachTodropdwon.render(attachToContainer); attachTodropdwon.render(attachToContainer);
attachSelectBoxStyler(attachTodropdwon, this.themeService); attachSelectBoxStyler(attachTodropdwon, this.themeService);

View File

@@ -12,12 +12,13 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview
import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification'; import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification';
import { SelectBox, ISelectBoxOptionsWithLabel } from 'sql/base/browser/ui/selectBox/selectBox'; import { SelectBox, ISelectBoxOptionsWithLabel } from 'sql/base/browser/ui/selectBox/selectBox';
import { INotebookModel, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces'; import { INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
import { CellType } from 'sql/parts/notebook/models/contracts'; import { CellType } from 'sql/parts/notebook/models/contracts';
import { NotebookComponent } from 'sql/parts/notebook/notebook.component'; import { NotebookComponent } from 'sql/parts/notebook/notebook.component';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/platform/connection/common/connectionManagement';
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils'; import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/platform/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { noKernel } from 'sql/workbench/services/notebook/common/sessionManager'; import { noKernel } from 'sql/workbench/services/notebook/common/sessionManager';
const msgLoading = localize('loading', 'Loading kernels...'); const msgLoading = localize('loading', 'Loading kernels...');
@@ -26,7 +27,7 @@ const attachToLabel: string = localize('AttachTo', 'Attach to: ');
const msgLoadingContexts = localize('loadingContexts', 'Loading contexts...'); const msgLoadingContexts = localize('loadingContexts', 'Loading contexts...');
const msgAddNewConnection = localize('addNewConnection', 'Add new connection'); const msgAddNewConnection = localize('addNewConnection', 'Add new connection');
const msgSelectConnection = localize('selectConnection', 'Select connection'); const msgSelectConnection = localize('selectConnection', 'Select connection');
const msgLocalHost = localize('localhost', 'Localhost'); const msgLocalHost = localize('localhost', 'localhost');
// Action to add a cell to notebook based on cell type(code/markdown). // Action to add a cell to notebook based on cell type(code/markdown).
export class AddCellAction extends Action { export class AddCellAction extends Action {
@@ -209,7 +210,8 @@ export class AttachToDropdown extends SelectBox {
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelRegistered: Promise<INotebookModel>, constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelRegistered: Promise<INotebookModel>,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IConnectionDialogService private _connectionDialogService: IConnectionDialogService, @IConnectionDialogService private _connectionDialogService: IConnectionDialogService,
@INotificationService private _notificationService: INotificationService) { @INotificationService private _notificationService: INotificationService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService) {
super([msgLoadingContexts], msgLoadingContexts, contextViewProvider, container, { labelText: attachToLabel, labelOnTop: false } as ISelectBoxOptionsWithLabel); super([msgLoadingContexts], msgLoadingContexts, contextViewProvider, container, { labelText: attachToLabel, labelOnTop: false } as ISelectBoxOptionsWithLabel);
if (modelRegistered) { if (modelRegistered) {
modelRegistered modelRegistered
@@ -219,8 +221,8 @@ export class AttachToDropdown extends SelectBox {
}); });
} }
this.onDidSelect(e => { this.onDidSelect(e => {
let connection = this.model.contexts.otherConnections.find((c) => c.options.host === e.selected); let connection = this.model.contexts.otherConnections.find((c) => c.serverName === e.selected);
this.doChangeContext(connection); this.doChangeContext(new ConnectionProfile(this._capabilitiesService, connection));
}); });
} }
@@ -228,54 +230,55 @@ export class AttachToDropdown extends SelectBox {
this.model = model; this.model = model;
model.contextsChanged(() => { model.contextsChanged(() => {
if (this.model.clientSession.kernel && this.model.clientSession.kernel.name) { if (this.model.clientSession.kernel && this.model.clientSession.kernel.name) {
let currentKernel = this.model.clientSession.kernel.name; let currentKernelSpec = this.model.specs.kernels.find(kernel => kernel.name === this.model.clientSession.kernel.name);
this.loadAttachToDropdown(this.model, currentKernel); this.loadAttachToDropdown(this.model, currentKernelSpec.display_name);
} }
}); });
} }
// Load "Attach To" dropdown with the values corresponding to Kernel dropdown // Load "Attach To" dropdown with the values corresponding to Kernel dropdown
public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> { public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> {
if (currentKernel === notebookConstants.python3 || currentKernel === noKernel) { let connProviderIds = this.model.getApplicableConnectionProviderIds(currentKernel);
if ((connProviderIds && connProviderIds.length === 0) || currentKernel === noKernel) {
this.setOptions([msgLocalHost]); this.setOptions([msgLocalHost]);
} }
else { else {
let hadoopConnections = this.getHadoopConnections(model); let connections = this.getConnections(model);
this.enable(); this.enable();
if (hadoopConnections.length === 1 && hadoopConnections[0] === msgAddNewConnection) { if (connections.length === 1 && connections[0] === msgAddNewConnection) {
hadoopConnections.unshift(msgSelectConnection); connections.unshift(msgSelectConnection);
this.selectWithOptionName(msgSelectConnection); this.selectWithOptionName(msgSelectConnection);
} }
else { else {
hadoopConnections.push(msgAddNewConnection); connections.push(msgAddNewConnection);
} }
this.setOptions(hadoopConnections); this.setOptions(connections);
} }
} }
//Get hadoop connections from context //Get connections from context
public getHadoopConnections(model: INotebookModel): string[] { public getConnections(model: INotebookModel): string[] {
let otherHadoopConnections: IConnectionProfile[] = []; let otherConnections: ConnectionProfile[] = [];
model.contexts.otherConnections.forEach((conn) => { otherHadoopConnections.push(conn); }); model.contexts.otherConnections.forEach((conn) => { otherConnections.push(conn); });
this.selectWithOptionName(model.contexts.defaultConnection.options.host); this.selectWithOptionName(model.contexts.defaultConnection.serverName);
otherHadoopConnections = this.setHadoopConnectionsList(model.contexts.defaultConnection, model.contexts.otherConnections); otherConnections = this.setConnectionsList(model.contexts.defaultConnection, model.contexts.otherConnections);
let hadoopConnections = otherHadoopConnections.map((context) => context.options.host); let connections = otherConnections.map((context) => context.serverName);
return hadoopConnections; return connections;
} }
private setHadoopConnectionsList(defaultHadoopConnection: IConnectionProfile, otherHadoopConnections: IConnectionProfile[]) { private setConnectionsList(defaultConnection: ConnectionProfile, otherConnections: ConnectionProfile[]) {
if (defaultHadoopConnection.options.host !== msgSelectConnection) { if (defaultConnection.serverName !== msgSelectConnection) {
otherHadoopConnections = otherHadoopConnections.filter(conn => conn.options.host !== defaultHadoopConnection.options.host); otherConnections = otherConnections.filter(conn => conn.serverName !== defaultConnection.serverName);
otherHadoopConnections.unshift(defaultHadoopConnection); otherConnections.unshift(defaultConnection);
if (otherHadoopConnections.length > 1) { if (otherConnections.length > 1) {
otherHadoopConnections = otherHadoopConnections.filter(val => val.options.host !== msgSelectConnection); otherConnections = otherConnections.filter(val => val.serverName !== msgSelectConnection);
} }
} }
return otherHadoopConnections; return otherConnections;
} }
public doChangeContext(connection?: IConnectionProfile): void { public doChangeContext(connection?: ConnectionProfile): void {
if (this.value === msgAddNewConnection) { if (this.value === msgAddNewConnection) {
this.openConnectionDialog(); this.openConnectionDialog();
} else { } else {
@@ -291,15 +294,15 @@ export class AttachToDropdown extends SelectBox {
**/ **/
public async openConnectionDialog(): Promise<void> { public async openConnectionDialog(): Promise<void> {
try { try {
//TODO: Figure out how to plumb through the correct provider here await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: this.model.getApplicableConnectionProviderIds(this.model.clientSession.kernel.name) }).then(connection => {
await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: [notebookConstants.hadoopKnoxProviderName] }).then(connection => {
let attachToConnections = this.values; let attachToConnections = this.values;
if (!connection) { if (!connection) {
this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name); this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name);
return; return;
} }
let connectedServer = connection.options[notebookConstants.hostPropName]; let connectionProfile = new ConnectionProfile(this._capabilitiesService, connection);
//Check to see if the same host is already there in dropdown. We only have host names in dropdown let connectedServer = connectionProfile.serverName;
//Check to see if the same server is already there in dropdown. We only have server names in dropdown
if (attachToConnections.some(val => val === connectedServer)) { if (attachToConnections.some(val => val === connectedServer)) {
this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name); this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name);
this.doChangeContext(); this.doChangeContext();
@@ -320,7 +323,7 @@ export class AttachToDropdown extends SelectBox {
this.select(index); this.select(index);
// Call doChangeContext to set the newly chosen connection in the model // Call doChangeContext to set the newly chosen connection in the model
this.doChangeContext(connection); this.doChangeContext(connectionProfile);
}); });
} }
catch (error) { catch (error) {

View File

@@ -13,7 +13,9 @@ import { Emitter, Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import * as resources from 'vs/base/common/resources'; import * as resources from 'vs/base/common/resources';
import * as sqlops from 'sqlops';
import { IStandardKernelWithProvider } from 'sql/parts/notebook/notebookUtils';
import { INotebookService, INotebookEditor } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookService, INotebookEditor } from 'sql/workbench/services/notebook/common/notebookService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
@@ -27,10 +29,13 @@ 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;
private _standardKernels: IStandardKernelWithProvider[];
private _defaultKernel: sqlops.nb.IKernelSpec;
constructor(public readonly notebookUri: URI, private readonly handle: number, private _isTrusted: boolean = false, private saveHandler?: ModeViewSaveHandler, provider?: string, private _providers?: string[]) { 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; this._providerId = provider;
this._standardKernels = [];
} }
public get providerId(): string { public get providerId(): string {
@@ -49,6 +54,28 @@ export class NotebookInputModel extends EditorModel {
this._providers = value; this._providers = value;
} }
public get standardKernels(): IStandardKernelWithProvider[] {
return this._standardKernels;
}
public set standardKernels(value: IStandardKernelWithProvider[]) {
value.forEach(kernel => {
this._standardKernels.push({
connectionProviderIds: kernel.connectionProviderIds,
name: kernel.name,
notebookProvider: kernel.notebookProvider
});
});
}
public get defaultKernel(): sqlops.nb.IKernelSpec {
return this._defaultKernel;
}
public set defaultKernel(kernel: sqlops.nb.IKernelSpec) {
this._defaultKernel = kernel;
}
get isTrusted(): boolean { get isTrusted(): boolean {
return this._isTrusted; return this._isTrusted;
} }
@@ -116,6 +143,14 @@ export class NotebookInput extends EditorInput {
return this._model.providers; return this._model.providers;
} }
public get standardKernels(): IStandardKernelWithProvider[] {
return this._model.standardKernels;
}
public get defaultKernel(): sqlops.nb.IKernelSpec {
return this._model.defaultKernel;
}
public getTypeId(): string { public getTypeId(): string {
return NotebookInput.ID; return NotebookInput.ID;
} }

View File

@@ -58,7 +58,28 @@ export function getProvidersForFileName(fileName: string, notebookService: INote
return providers; return providers;
} }
export function getStandardKernelsForProvider(providerId: string, notebookService: INotebookService) : IStandardKernelWithProvider[] {
if (!providerId || !notebookService) {
return [];
}
let standardKernels = notebookService.getStandardKernelsForProvider(providerId);
standardKernels.forEach(kernel => {
Object.assign(<IStandardKernelWithProvider>kernel, {
name: kernel.name,
connectionProviderIds: kernel.connectionProviderIds,
notebookProvider: providerId
});
});
return <IStandardKernelWithProvider[]>(standardKernels);
}
// Private feature flag to enable Sql Notebook experience // Private feature flag to enable Sql Notebook experience
export function sqlNotebooksEnabled() { export function sqlNotebooksEnabled() {
return process.env['SQLOPS_SQL_NOTEBOOK'] !== undefined; return process.env['SQLOPS_SQL_NOTEBOOK'] !== undefined;
} }
export interface IStandardKernelWithProvider {
readonly name: string;
readonly connectionProviderIds: string[];
readonly notebookProvider: string;
}

View File

@@ -325,7 +325,7 @@ output-component .jp-RenderedHTMLCommon table {
border: none; border: none;
color: var(--jp-ui-font-color1); color: var(--jp-ui-font-color1);
font-size: 12px; font-size: 12px;
table-layout: fixed; table-layout: auto;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
@@ -338,7 +338,7 @@ output-component .jp-RenderedHTMLCommon thead {
output-component .jp-RenderedHTMLCommon td, output-component .jp-RenderedHTMLCommon td,
output-component .jp-RenderedHTMLCommon th, output-component .jp-RenderedHTMLCommon th,
output-component .jp-RenderedHTMLCommon tr { output-component .jp-RenderedHTMLCommon tr {
text-align: right; text-align: left;
vertical-align: middle; vertical-align: middle;
padding: 0.5em 0.5em; padding: 0.5em 0.5em;
line-height: normal; line-height: normal;
@@ -370,6 +370,7 @@ output-component .jp-RenderedHTMLCommon tbody tr:hover {
output-component .jp-RenderedHTMLCommon table { output-component .jp-RenderedHTMLCommon table {
margin-bottom: 1em; margin-bottom: 1em;
display: table-row;
} }
output-component .jp-RenderedHTMLCommon p { output-component .jp-RenderedHTMLCommon p {

View File

@@ -1,16 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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/';
}

View File

@@ -1594,6 +1594,11 @@ declare module 'sqlops' {
* Optional ID indicating the initial connection to use for this editor * Optional ID indicating the initial connection to use for this editor
*/ */
connectionId?: string; connectionId?: string;
/**
* Default kernel for notebook
*/
defaultKernel?: nb.IKernelSpec;
} }
/** /**
@@ -1666,9 +1671,14 @@ declare module 'sqlops' {
*/ */
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable; export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
export interface IStandardKernel {
readonly name: string;
readonly connectionProviderIds: string[];
}
export interface NotebookProvider { export interface NotebookProvider {
readonly providerId: string; readonly providerId: string;
readonly standardKernels: string[]; readonly standardKernels: IStandardKernel[];
getNotebookManager(notebookUri: vscode.Uri): Thenable<NotebookManager>; getNotebookManager(notebookUri: vscode.Uri): Thenable<NotebookManager>;
handleNotebookClosed(notebookUri: vscode.Uri): void; handleNotebookClosed(notebookUri: vscode.Uri): void;
} }
@@ -1945,6 +1955,10 @@ declare module 'sqlops' {
defaultKernelLoaded?: boolean; defaultKernelLoaded?: boolean;
changeKernel(kernelInfo: IKernelSpec): Thenable<IKernel>; changeKernel(kernelInfo: IKernelSpec): Thenable<IKernel>;
configureKernel(kernelInfo: IKernelSpec): Thenable<void>;
configureConnection(connection: IConnectionProfile): Thenable<void>;
} }
export interface ISessionOptions { export interface ISessionOptions {

View File

@@ -125,6 +125,16 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
return session.changeKernel(kernelInfo).then(kernel => this.saveKernel(kernel)); return session.changeKernel(kernelInfo).then(kernel => this.saveKernel(kernel));
} }
$configureKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<void> {
let session = this._getAdapter<sqlops.nb.ISession>(sessionId);
return session.configureKernel(kernelInfo).then(() => null);
}
$configureConnection(sessionId: number, connection: sqlops.IConnectionProfile): Thenable<void> {
let session = this._getAdapter<sqlops.nb.ISession>(sessionId);
return session.configureConnection(connection).then(() => null);
}
$getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply> { $getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId); let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
return kernel.ready.then(success => kernel.info); return kernel.ready.then(success => kernel.info);

View File

@@ -168,6 +168,7 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
options.position = showOptions.viewColumn; options.position = showOptions.viewColumn;
options.providerId = showOptions.providerId; options.providerId = showOptions.providerId;
options.connectionId = showOptions.connectionId; options.connectionId = showOptions.connectionId;
options.defaultKernel = showOptions.defaultKernel;
} }
let id = await this._proxy.$tryShowNotebookDocument(uri, options); let id = await this._proxy.$tryShowNotebookDocument(uri, options);
let editor = this.getEditor(id); let editor = this.getEditor(id);

View File

@@ -288,11 +288,30 @@ class SessionWrapper implements sqlops.nb.ISession {
return this.doChangeKernel(kernelInfo); return this.doChangeKernel(kernelInfo);
} }
configureKernel(kernelInfo: sqlops.nb.IKernelSpec): Thenable<void> {
return this.doConfigureKernel(kernelInfo);
}
configureConnection(connection: sqlops.IConnectionProfile): Thenable<void> {
if (connection['capabilitiesService'] !== undefined) {
connection['capabilitiesService'] = undefined;
}
return this.doConfigureConnection(connection);
}
private async doChangeKernel(kernelInfo: sqlops.nb.IKernelSpec): Promise<sqlops.nb.IKernel> { private async doChangeKernel(kernelInfo: sqlops.nb.IKernelSpec): Promise<sqlops.nb.IKernel> {
let kernelDetails = await this._proxy.ext.$changeKernel(this.sessionDetails.sessionId, kernelInfo); let kernelDetails = await this._proxy.ext.$changeKernel(this.sessionDetails.sessionId, kernelInfo);
this._kernel = new KernelWrapper(this._proxy, kernelDetails); this._kernel = new KernelWrapper(this._proxy, kernelDetails);
return this._kernel; return this._kernel;
} }
private async doConfigureKernel(kernelInfo: sqlops.nb.IKernelSpec): Promise<void> {
await this._proxy.ext.$configureKernel(this.sessionDetails.sessionId, kernelInfo);
}
private async doConfigureConnection(connection: sqlops.IConnectionProfile): Promise<void> {
await this._proxy.ext.$configureConnection(this.sessionDetails.sessionId, connection);
}
} }
class KernelWrapper implements sqlops.nb.IKernel { class KernelWrapper implements sqlops.nb.IKernel {

View File

@@ -25,7 +25,7 @@ import {
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput'; import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { getProvidersForFileName } from 'sql/parts/notebook/notebookUtils'; import { getProvidersForFileName, getStandardKernelsForProvider } 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';
@@ -332,6 +332,11 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
} }
model.providers = providers; model.providers = providers;
model.providerId = providerId; model.providerId = providerId;
model.defaultKernel = options && options.defaultKernel;
model.providers.forEach(provider => {
let standardKernels = getStandardKernelsForProvider(provider, this._notebookService);
model.standardKernels = standardKernels;
});
let input = this._instantiationService.createInstance(NotebookInput, undefined, model); let input = this._instantiationService.createInstance(NotebookInput, undefined, model);
let editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position)); let editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));

View File

@@ -773,6 +773,8 @@ export interface ExtHostNotebookShape {
// Session APIs // Session APIs
$changeKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<INotebookKernelDetails>; $changeKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<INotebookKernelDetails>;
$configureKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<void>;
$configureConnection(sessionId: number, connection: sqlops.IConnectionProfile): Thenable<void>;
// Kernel APIs // Kernel APIs
$getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply>; $getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply>;
@@ -829,6 +831,7 @@ export interface INotebookShowOptions {
preview?: boolean; preview?: boolean;
providerId?: string; providerId?: string;
connectionId?: string; connectionId?: string;
defaultKernel?: sqlops.nb.IKernelSpec;
} }
export interface ExtHostNotebookDocumentsAndEditorsShape { export interface ExtHostNotebookDocumentsAndEditorsShape {

View File

@@ -9,6 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform'; import * as platform from 'vs/platform/registry/common/platform';
import * as sqlops from 'sqlops';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
export const Extensions = { export const Extensions = {
@@ -18,7 +19,7 @@ export const Extensions = {
export interface NotebookProviderRegistration { export interface NotebookProviderRegistration {
provider: string; provider: string;
fileExtensions: string | string[]; fileExtensions: string | string[];
standardKernels: string | string[]; standardKernels: sqlops.nb.IStandardKernel | sqlops.nb.IStandardKernel[];
} }
let notebookProviderType: IJSONSchema = { let notebookProviderType: IJSONSchema = {
@@ -44,13 +45,40 @@ let notebookProviderType: IJSONSchema = {
standardKernels: { standardKernels: {
description: localize('carbon.extension.contributes.notebook.standardKernels', 'What kernels should be standard with this notebook provider'), description: localize('carbon.extension.contributes.notebook.standardKernels', 'What kernels should be standard with this notebook provider'),
oneOf: [ oneOf: [
{ type: 'string' },
{ {
type: 'object',
properties: {
name: {
type: 'string',
},
connectionProviderIds: {
type: 'array', type: 'array',
items: { items: {
type: 'string' type: 'string'
} }
} }
}
},
{
type: 'array',
items: {
type: 'object',
items: {
type: 'object',
properties: {
name: {
type: 'string',
},
connectionProviderIds: {
type: 'array',
items: {
type: 'string'
}
}
}
}
}
}
] ]
} }
} }

View File

@@ -48,6 +48,8 @@ export interface INotebookService {
getProvidersForFileType(fileType: string): string[]; getProvidersForFileType(fileType: string): string[];
getStandardKernelsForProvider(provider: string): sqlops.nb.IStandardKernel[];
/** /**
* 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
* run cells in a notebook. * run cells in a notebook.

View File

@@ -80,6 +80,7 @@ export class NotebookService extends Disposable implements INotebookService {
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 _providerToStandardKernels = new Map<string, nb.IStandardKernel[]>();
private _registrationComplete = new Deferred<void>(); private _registrationComplete = new Deferred<void>();
private _isRegistrationComplete = false; private _isRegistrationComplete = false;
@@ -121,6 +122,9 @@ export class NotebookService extends Disposable implements INotebookService {
this.addFileProvider(registration.fileExtensions, registration); this.addFileProvider(registration.fileExtensions, registration);
} }
} }
if (registration.standardKernels) {
this.addStandardKernels(registration);
}
} }
registerProvider(providerId: string, instance: INotebookProvider): void { registerProvider(providerId: string, instance: INotebookProvider): void {
@@ -154,6 +158,26 @@ export class NotebookService extends Disposable implements INotebookService {
this._fileToProviders.set(fileType.toUpperCase(), providers); this._fileToProviders.set(fileType.toUpperCase(), providers);
} }
// Standard kernels are contributed where a list of kernels are defined that can be shown
// in the kernels dropdown list before a SessionManager has been started; this way,
// every NotebookProvider doesn't need to have an active SessionManager in order to contribute
// kernels to the dropdown
private addStandardKernels(provider: NotebookProviderRegistration) {
let providerUpperCase = provider.provider.toUpperCase();
let standardKernels = this._providerToStandardKernels.get(providerUpperCase);
if (!standardKernels) {
standardKernels = [];
}
if (Array.isArray(provider.standardKernels)) {
provider.standardKernels.forEach(kernel => {
standardKernels.push(kernel);
});
} else {
standardKernels.push(provider.standardKernels);
}
this._providerToStandardKernels.set(providerUpperCase, standardKernels);
}
getSupportedFileExtensions(): string[] { getSupportedFileExtensions(): string[] {
return Array.from(this._fileToProviders.keys()); return Array.from(this._fileToProviders.keys());
} }
@@ -165,6 +189,10 @@ export class NotebookService extends Disposable implements INotebookService {
return providers ? providers.map(provider => provider.provider) : undefined; return providers ? providers.map(provider => provider.provider) : undefined;
} }
getStandardKernelsForProvider(provider: string): nb.IStandardKernel[] {
return this._providerToStandardKernels.get(provider.toUpperCase());
}
public shutdown(): void { public shutdown(): void {
this._managersMap.forEach(manager => { this._managersMap.forEach(manager => {
manager.forEach(m => { manager.forEach(m => {
@@ -337,7 +365,7 @@ export class NotebookService extends Disposable implements INotebookService {
notebookRegistry.registerNotebookProvider({ notebookRegistry.registerNotebookProvider({
provider: sqlProvider.providerId, provider: sqlProvider.providerId,
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE, fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
standardKernels: ['SQL'] standardKernels: { name: 'SQL', connectionProviderIds: ['MSSQL'] }
}); });
} }
} }
@@ -444,5 +472,4 @@ export class SqlNotebookManager implements INotebookManager {
public get sessionManager(): nb.SessionManager { public get sessionManager(): nb.SessionManager {
return this._sessionManager; return this._sessionManager;
} }
} }

View File

@@ -7,6 +7,7 @@
import { nb } from 'sqlops'; import { nb } from 'sqlops';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces'; import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
export const noKernel: string = localize('noKernel', 'No Kernel'); export const noKernel: string = localize('noKernel', 'No Kernel');
const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured'); const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured');
@@ -91,6 +92,15 @@ export class EmptySession implements nb.ISession {
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> { changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
return Promise.resolve(this.kernel); return Promise.resolve(this.kernel);
} }
// No kernel config necessary for empty session
configureKernel(kernelInfo: nb.IKernelSpec): Thenable<void> {
return Promise.resolve();
}
configureConnection(connection: ConnectionProfile): Thenable<void> {
return Promise.resolve();
}
} }
class EmptyKernel implements nb.IKernel { class EmptyKernel implements nb.IKernel {

View File

@@ -14,8 +14,9 @@ import Severity from 'vs/base/common/severity';
import * as Utils from 'sql/platform/connection/common/utils'; import * as Utils from 'sql/platform/connection/common/utils';
import { Deferred } from 'sql/base/common/promise'; import { Deferred } from 'sql/base/common/promise';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export const sqlKernel: string = localize('sqlKernel', 'SQL'); export const sqlKernel: string = localize('sqlKernel', 'SQL');
export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error"); export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error");
@@ -63,6 +64,7 @@ export class SqlSessionManager implements nb.SessionManager {
export class SqlSession implements nb.ISession { export class SqlSession implements nb.ISession {
private _kernel: SqlKernel; private _kernel: SqlKernel;
private _defaultKernelLoaded = false; private _defaultKernelLoaded = false;
private _currentConnection: IConnectionProfile;
public set defaultKernelLoaded(value) { public set defaultKernelLoaded(value) {
this._defaultKernelLoaded = value; this._defaultKernelLoaded = value;
@@ -107,12 +109,25 @@ export class SqlSession implements nb.ISession {
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> { changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
return Promise.resolve(this.kernel); return Promise.resolve(this.kernel);
} }
configureKernel(kernelInfo: nb.IKernelSpec): Thenable<void> {
return Promise.resolve();
}
configureConnection(connection: ConnectionProfile): Thenable<void> {
if (this._kernel) {
this._kernel.connection = connection;
}
return Promise.resolve();
}
} }
class SqlKernel extends Disposable implements nb.IKernel { class SqlKernel extends Disposable implements nb.IKernel {
private _queryRunner: QueryRunner; private _queryRunner: QueryRunner;
private _columns: IDbColumn[]; private _columns: IDbColumn[];
private _rows: DbCellValue[][]; private _rows: DbCellValue[][];
private _currentConnection: IConnectionProfile;
static kernelId: number = 0;
constructor( @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, constructor( @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@@ -121,7 +136,7 @@ class SqlKernel extends Disposable implements nb.IKernel {
} }
public get id(): string { public get id(): string {
return '-1'; return (SqlKernel.kernelId++).toString();
} }
public get name(): string { public get name(): string {
@@ -159,6 +174,12 @@ class SqlKernel extends Disposable implements nb.IKernel {
return info; return info;
} }
public set connection(conn: IConnectionProfile) {
this._currentConnection = conn;
this._queryRunner = undefined;
}
getSpec(): Thenable<nb.IKernelSpec> { getSpec(): Thenable<nb.IKernelSpec> {
return Promise.resolve(sqlKernelSpec); return Promise.resolve(sqlKernelSpec);
} }
@@ -167,11 +188,10 @@ class SqlKernel extends Disposable implements nb.IKernel {
if (this._queryRunner) { if (this._queryRunner) {
this._queryRunner.runQuery(content.code); this._queryRunner.runQuery(content.code);
} else { } else {
let connections = this._connectionManagementService.getActiveConnections(); let connectionUri = Utils.generateUri(this._currentConnection, 'notebook');
let connectionProfile = connections.find(connection => connection.providerName === mssqlProviderName);
let connectionUri = Utils.generateUri(connectionProfile, 'notebook');
this._queryRunner = this._instantiationService.createInstance(QueryRunner, connectionUri, undefined); this._queryRunner = this._instantiationService.createInstance(QueryRunner, connectionUri, undefined);
this._connectionManagementService.connect(connectionProfile, connectionUri).then((result) => { this._connectionManagementService.connect(this._currentConnection, connectionUri).then((result) =>
{
this.addQueryEventListeners(this._queryRunner); this.addQueryEventListeners(this._queryRunner);
this._queryRunner.runQuery(content.code); this._queryRunner.runQuery(content.code);
}); });
@@ -186,7 +206,9 @@ class SqlKernel extends Disposable implements nb.IKernel {
} }
interrupt(): Thenable<void> { interrupt(): Thenable<void> {
return Promise.resolve(undefined); // TODO: figure out what to do with the QueryCancelResult
return this._queryRunner.cancelQuery().then((cancelResult) => {
});
} }
private addQueryEventListeners(queryRunner: QueryRunner): void { private addQueryEventListeners(queryRunner: QueryRunner): void {
@@ -234,7 +256,11 @@ export class SQLFuture extends Disposable implements FutureInternal {
get inProgress(): boolean { get inProgress(): boolean {
return !this._queryRunner.hasCompleted; return !this._queryRunner.hasCompleted;
} }
set inProgress(val: boolean) {
if (this._queryRunner && !val) {
this._queryRunner.cancelQuery();
}
}
get msg(): nb.IMessage { get msg(): nb.IMessage {
return this._msg; return this._msg;
} }

View File

@@ -56,6 +56,9 @@ export class NotebookModelStub implements INotebookModel {
get providerId(): string { get providerId(): string {
throw new Error('method not implemented.'); throw new Error('method not implemented.');
} }
get applicableConnectionProviderIds(): 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.');
} }
@@ -77,6 +80,9 @@ export class NotebookModelStub implements INotebookModel {
pushEditOperations(edits: ISingleNotebookEditOperation[]): void { pushEditOperations(edits: ISingleNotebookEditOperation[]): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
getApplicableConnectionProviderIds(kernelName: string): string[] {
throw new Error('Method not implemented.');
}
} }
export class NotebookManagerStub implements INotebookManager { export class NotebookManagerStub implements INotebookManager {

View File

@@ -25,6 +25,8 @@ import { Deferred } from 'sql/base/common/promise';
import { ConnectionManagementService } from 'sql/platform/connection/common/connectionManagementService'; import { ConnectionManagementService } from 'sql/platform/connection/common/connectionManagementService';
import { Memento } from 'vs/workbench/common/memento'; import { Memento } from 'vs/workbench/common/memento';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
let expectedNotebookContent: nb.INotebookContents = { let expectedNotebookContent: nb.INotebookContents = {
cells: [{ cells: [{
@@ -71,8 +73,9 @@ let mockClientSession: TypeMoq.Mock<IClientSession>;
let sessionReady: Deferred<void>; let sessionReady: Deferred<void>;
let mockModelFactory: TypeMoq.Mock<ModelFactory>; let mockModelFactory: TypeMoq.Mock<ModelFactory>;
let notificationService: TypeMoq.Mock<INotificationService>; let notificationService: TypeMoq.Mock<INotificationService>;
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>;
describe('notebook model', function (): void { describe('notebook model', function(): void {
let notebookManagers = [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>;
@@ -80,6 +83,7 @@ describe('notebook model', function (): void {
beforeEach(() => { beforeEach(() => {
sessionReady = new Deferred<void>(); sessionReady = new Deferred<void>();
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose); notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
capabilitiesService = TypeMoq.Mock.ofType(CapabilitiesTestService);
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, ''); memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0); memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined); queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined);
@@ -90,7 +94,10 @@ describe('notebook model', function (): void {
notebookManagers, notebookManagers,
notificationService: notificationService.object, notificationService: notificationService.object,
connectionService: queryConnectionService.object, connectionService: queryConnectionService.object,
providerId: 'jupyter' providerId: 'SQL',
standardKernels: [{ name: 'SQL', connectionProviderIds: ['MSSQL'], notebookProvider: 'sql' }],
defaultKernel: undefined,
capabilitiesService: capabilitiesService.object
}; };
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(() => {
@@ -104,7 +111,7 @@ describe('notebook model', function (): void {
}); });
}); });
it('Should create no cells if model has no contents', async function (): Promise<void> { it('Should create no cells if model has no contents', async function(): Promise<void> {
// Given an empty notebook // Given an empty notebook
let emptyNotebook: nb.INotebookContents = { let emptyNotebook: nb.INotebookContents = {
cells: [], cells: [],
@@ -130,7 +137,7 @@ describe('notebook model', function (): void {
should(model.cells).have.length(0); should(model.cells).have.length(0);
}); });
it('Should throw if model load fails', async function (): Promise<void> { it('Should throw if model load fails', async function(): Promise<void> {
// Given a call to get Contents fails // Given a call to get Contents fails
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);
@@ -145,7 +152,7 @@ describe('notebook model', function (): void {
should(model.inErrorState).be.true(); should(model.inErrorState).be.true();
}); });
it('Should convert cell info to CellModels', async function (): Promise<void> { it('Should convert cell info to CellModels', async function(): Promise<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));
@@ -161,7 +168,7 @@ describe('notebook model', function (): void {
should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source); should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source);
}); });
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));
notebookManagers[0].contentManager = mockContentManager.object; notebookManagers[0].contentManager = mockContentManager.object;
@@ -172,7 +179,7 @@ describe('notebook model', function (): void {
sessionReady.resolve(); sessionReady.resolve();
let sessionFired = false; let sessionFired = false;
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>>{ let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
factory: mockModelFactory.object factory: mockModelFactory.object
}); });
let model = new NotebookModel(options); let model = new NotebookModel(options);
@@ -190,7 +197,7 @@ describe('notebook model', function (): void {
should(sessionFired).be.false(); should(sessionFired).be.false();
}); });
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));
notebookManagers[0].contentManager = mockContentManager.object; notebookManagers[0].contentManager = mockContentManager.object;
@@ -205,7 +212,7 @@ describe('notebook model', function (): void {
sessionReady.resolve(); sessionReady.resolve();
let actualSession: IClientSession = undefined; let actualSession: IClientSession = undefined;
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>>{ let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
factory: mockModelFactory.object factory: mockModelFactory.object
}); });
let model = new NotebookModel(options, false); let model = new NotebookModel(options, false);
@@ -225,14 +232,14 @@ describe('notebook model', function (): void {
should(model.clientSession).equal(mockClientSession.object); should(model.clientSession).equal(mockClientSession.object);
}); });
it('Should sanitize kernel display name when IP is included', async function (): Promise<void> { it('Should sanitize kernel display name when IP is included', async function(): Promise<void> {
let model = new NotebookModel(defaultModelOptions); let model = new NotebookModel(defaultModelOptions);
let displayName = 'PySpark (1.1.1.1)'; let displayName = 'PySpark (1.1.1.1)';
let sanitizedDisplayName = model.sanitizeDisplayName(displayName); let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
should(sanitizedDisplayName).equal('PySpark'); should(sanitizedDisplayName).equal('PySpark');
}); });
it('Should sanitize kernel display name properly when IP is not included', async function (): Promise<void> { it('Should sanitize kernel display name properly when IP is not included', async function(): Promise<void> {
let model = new NotebookModel(defaultModelOptions); let model = new NotebookModel(defaultModelOptions);
let displayName = 'PySpark'; let displayName = 'PySpark';
let sanitizedDisplayName = model.sanitizeDisplayName(displayName); let sanitizedDisplayName = model.sanitizeDisplayName(displayName);

View File

@@ -122,7 +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']; standardKernels: sqlops.nb.IStandardKernel[] = [{name: 'fakeKernel', connectionProviderIds: ['MSSQL']}];
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.');

View File

@@ -149,6 +149,12 @@ class ExtHostNotebookStub implements ExtHostNotebookShape {
$changeKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<INotebookKernelDetails> { $changeKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<INotebookKernelDetails> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
$configureKernel(sessionId: number, kernelInfo: sqlops.nb.IKernelSpec): Thenable<void> {
throw new Error('Method not implemented.');
}
$configureConnection(sessionId: number, conneection: sqlops.IConnectionProfile): Thenable<void> {
throw new Error('Method not implemented.');
}
$getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply> { $getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }