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

View File

@@ -20,8 +20,6 @@ let modelId = 0;
export class CellModel implements ICellModel {
private static LanguageMapping: Map<string, string>;
private _cellType: nb.CellType;
private _source: string;
private _language: string;
@@ -37,7 +35,6 @@ export class CellModel implements ICellModel {
constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) {
this.id = `${modelId++}`;
CellModel.CreateLanguageMappings();
if (cellData) {
// Read in contents if available
this.fromJSON(cellData);
@@ -238,9 +235,9 @@ export class CellModel implements ICellModel {
try {
let result = output as nb.IDisplayResult;
if (result && result.data && result.data['text/html']) {
let nbm = (this as CellModel).options.notebook as NotebookModel;
if (nbm.hadoopConnection) {
let host = nbm.hadoopConnection.host;
let model = (this as CellModel).options.notebook as NotebookModel;
if (model.activeConnection) {
let host = model.activeConnection.serverName;
let html = result.data['text/html'];
html = html.replace(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, function (a, b, c) {
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 {
if (this._options && this._options.notebook && 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
let languageInfo = this.languageInfo;
if (languageInfo) {
if (languageInfo.name) {
// check the LanguageMapping to determine if a mapping is necessary (example 'pyspark' -> 'python')
if (CellModel.LanguageMapping[languageInfo.name]) {
this._language = CellModel.LanguageMapping[languageInfo.name];
if (languageInfo.codemirror_mode) {
let codeMirrorMode: nb.ICodeMirrorMode = <nb.ICodeMirrorMode>(languageInfo.codemirror_mode);
if (codeMirrorMode && codeMirrorMode.name) {
this._language = codeMirrorMode.name;
}
else {
this._language = languageInfo.name;
}
}
else if (languageInfo.mimetype) {
} else if (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 * as notebookUtils from '../notebookUtils';
import * as sparkUtils from '../spark/sparkUtils';
import { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
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,
@@ -49,7 +47,6 @@ export class ClientSession implements IClientSession {
private _session: nb.ISession;
private isServerStarted: boolean;
private notebookManager: INotebookManager;
private _connection: NotebookConnection;
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
constructor(private options: IClientSessionOptions) {
@@ -60,10 +57,8 @@ export class ClientSession implements IClientSession {
this._kernelChangeCompleted = new Deferred<void>();
}
public async initialize(connection?: NotebookConnection): Promise<void> {
public async initialize(): Promise<void> {
try {
this._kernelConfigActions.push((kernelName: string) => { return this.runTasksBeforeSessionStart(kernelName); });
this._connection = connection;
this._serverLoadFinished = this.startServer();
await this._serverLoadFinished;
await this.initializeSession();
@@ -114,7 +109,7 @@ export class ClientSession implements IClientSession {
} catch (err) {
// TODO move registration
if (err && err.response && err.response.status === 501) {
this.options.notificationService.warn(nls.localize('sparkKernelRequiresConnection', 'Kernel {0} was not found. The default kernel will be used instead.', kernelName));
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({
path: this.notebookUri.fsPath,
kernelName: undefined
@@ -256,41 +251,22 @@ export class ClientSession implements IClientSession {
return kernel;
}
public async runTasksBeforeSessionStart(kernelName: string): Promise<void> {
// TODO we should move all Spark-related code to SparkMagicContext
if (this._session && this._connection && this.isSparkKernel(kernelName)) {
// TODO may need to reenable a way to get the credential
// await this._connection.getCredential();
// %_do_not_call_change_endpoint is a SparkMagic command that lets users change endpoint options,
// such as user/profile/host name/auth type
let server = URI.parse(sparkUtils.getLivyUrl(this._connection.host, this._connection.knoxport)).toString();
let doNotCallChangeEndpointParams =
`%_do_not_call_change_endpoint --username=${this._connection.user} --password=${this._connection.password} --server=${server} --auth=Basic_Access`;
let future = this._session.kernel.requestExecute({
code: doNotCallChangeEndpointParams
}, true);
await future.done;
public async configureKernel(options: nb.IKernelSpec): Promise<void> {
if (this._session) {
await this._session.configureKernel(options);
}
}
public async updateConnection(connection: NotebookConnection): Promise<void> {
public async updateConnection(connection: IConnectionProfile): Promise<void> {
if (!this.kernel) {
// TODO is there any case where skipping causes errors? So far it seems like it gets called twice
return;
}
this._connection = (connection.connectionProfile.id !== '-1') ? connection : this._connection;
// if kernel is not set, don't run kernel config actions
// this should only occur when a cell is cancelled, which interrupts the kernel
if (this.kernel && this.kernel.name) {
await this.runKernelConfigActions(this.kernel.name);
if (connection.id !== '-1') {
await this._session.configureConnection(connection);
}
}
isSparkKernel(kernelName: string): any {
return kernelName && kernelName.toLowerCase().indexOf('spark') > -1;
}
/**
* 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 { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
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 { 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 {
notebookUri: URI;
@@ -131,7 +133,7 @@ export interface IClientSession extends IDisposable {
* This will optionally start a session if the kernel preferences
* indicate this is desired
*/
initialize(connection?: NotebookConnection): Promise<void>;
initialize(connection?: IConnectionProfile): Promise<void>;
/**
* Change the current kernel associated with the document.
@@ -140,6 +142,13 @@ export interface IClientSession extends IDisposable {
options: nb.IKernelSpec
): Promise<nb.IKernel>;
/**
* Configure the current kernel associated with the document.
*/
configureKernel(
options: nb.IKernelSpec
): Promise<void>;
/**
* Kill the kernel and shutdown the session.
*
@@ -191,12 +200,12 @@ export interface IClientSession extends IDisposable {
/**
* Updates the connection
*/
updateConnection(connection: NotebookConnection): void;
updateConnection(connection: IConnectionProfile): void;
}
export interface IDefaultConnection {
defaultConnection: IConnectionProfile;
otherConnections: IConnectionProfile[];
defaultConnection: ConnectionProfile;
otherConnections: ConnectionProfile[];
}
/**
@@ -348,6 +357,8 @@ export interface INotebookModel {
* @param edits The edit operations to perform
*/
pushEditOperations(edits: ISingleNotebookEditOperation[]): void;
getApplicableConnectionProviderIds(kernelName: string): string[];
}
export interface NotebookContentChange {
@@ -418,16 +429,14 @@ export interface INotebookModelOptions {
notebookManagers: INotebookManager[];
providerId: string;
standardKernels: IStandardKernelWithProvider[];
defaultKernel: nb.IKernelSpec;
notificationService: INotificationService;
connectionService: IConnectionManagementService;
capabilitiesService: ICapabilitiesService;
}
// TODO would like to move most of these constants to an extension
export namespace notebookConstants {
export const hadoopKnoxProviderName = 'HADOOP_KNOX';
export const python3 = 'python3';
export const python3DisplayName = 'Python 3';
export const defaultSparkKernel = 'pyspark3kernel';
export const hostPropName = 'host';
export const SQL = 'SQL';
}

View File

@@ -11,7 +11,6 @@ import { localize } from 'vs/nls';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export namespace constants {
export const hostPropName = 'host';
export const userPropName = 'user';
export const knoxPortPropName = 'knoxport';
export const clusterPropName = 'clustername';
@@ -52,7 +51,7 @@ export class NotebookConnection {
* preference to the built in port.
*/
private ensureHostAndPort(): void {
this._host = this.connectionProfile.options[constants.hostPropName];
this._host = this.connectionProfile.serverName;
this._knoxPort = NotebookConnection.getKnoxPortOrDefault(this.connectionProfile);
// determine whether the host has either a ',' or ':' in it
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 * as notebookUtils from '../notebookUtils';
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 { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { INotification, Severity } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network';
import URI from 'vs/base/common/uri';
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,
@@ -58,11 +58,12 @@ export class NotebookModel extends Disposable implements INotebookModel {
private _savedKernelInfo: nb.IKernelInfo;
private readonly _nbformat: number = nbversion.MAJOR_VERSION;
private readonly _nbformatMinor: number = nbversion.MINOR_VERSION;
private _hadoopConnection: NotebookConnection;
private _defaultKernel: nb.IKernelSpec;
private _activeConnection: ConnectionProfile;
private _activeCell: ICellModel;
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) {
super();
@@ -74,6 +75,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
this._trustedMode = false;
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[] {
@@ -171,10 +177,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
return this._trustedMode;
}
public get isNewNotebook(): boolean {
return this._isNewNotebook;
}
public get providerId(): string {
return this._providerId;
}
@@ -188,8 +190,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
}
public get hadoopConnection(): NotebookConnection {
return this._hadoopConnection;
public get activeConnection(): IConnectionProfile {
return this._activeConnection;
}
/**
@@ -207,6 +209,14 @@ export class NotebookModel extends Disposable implements INotebookModel {
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> {
try {
this._trustedMode = isTrusted;
@@ -223,9 +233,12 @@ export class NotebookModel extends Disposable implements INotebookModel {
version: ''
};
if (contents) {
this._isNewNotebook = false;
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
this._savedKernelInfo = this.getSavedKernelInfo(contents);
this.setProviderIdForKernel(this._savedKernelInfo);
if (this._savedKernelInfo) {
this._defaultKernel = this._savedKernelInfo;
}
if (contents.cells && contents.cells.length > 0) {
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) {
this._activeClientSession = clientSession;
}
let profile = this.connectionProfile as IConnectionProfile;
let profile = new ConnectionProfile(this.notebookOptions.capabilitiesService, this.connectionProfile);
if (this.isValidKnoxConnection(profile)) {
this._hadoopConnection = new NotebookConnection(this.connectionProfile);
if (this.isValidConnection(profile)) {
this._activeConnection = profile;
} else {
this._hadoopConnection = undefined;
this._activeConnection = undefined;
}
clientSession.initialize(this._hadoopConnection);
clientSession.initialize(this._activeConnection);
this._sessionLoadFinished = clientSession.ready.then(async () => {
if (clientSession.isInErrorState) {
this.setErrorState(clientSession.errorMessage);
@@ -360,8 +373,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
});
}
private isValidKnoxConnection(profile: IConnectionProfile | connection.Connection) {
return profile && profile.providerName === notebookConstants.hadoopKnoxProviderName && profile.options[notebookConstants.hostPropName] !== undefined;
private isValidConnection(profile: IConnectionProfile | connection.Connection) {
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 {
@@ -380,45 +395,49 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> {
this.findProviderIdForKernel(kernelSpec);
return this._activeClientSession.changeKernel(kernelSpec)
.then((kernel) => {
kernel.ready.then(() => {
if (kernel.info) {
this.updateLanguageInfo(kernel.info.language_info);
}
}, err => undefined);
return this.updateKernelInfo(kernel);
}).catch((err) => {
this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err)));
// TODO should revert kernels dropdown
});
this.setProviderIdForKernel(kernelSpec);
if (this._activeClientSession && this._activeClientSession.isReady) {
return this._activeClientSession.changeKernel(kernelSpec)
.then((kernel) => {
this.updateKernelInfo(kernel);
kernel.ready.then(() => {
if (kernel.info) {
this.updateLanguageInfo(kernel.info.language_info);
}
}, err => undefined);
}).catch((err) => {
this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err)));
// 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 {
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;
}
SparkMagicContexts.configureContext();
this._hadoopConnection = new NotebookConnection(newConnection);
this.refreshConnections(newConnection);
this._activeClientSession.updateConnection(this._hadoopConnection);
let newConnectionProfile = new ConnectionProfile(this.notebookOptions.capabilitiesService, newConnection);
this._activeConnection = newConnectionProfile;
this.refreshConnections(newConnectionProfile);
this._activeClientSession.updateConnection(this._activeConnection);
} catch (err) {
let msg = notebookUtils.getErrorMessage(err);
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
}
}
private refreshConnections(newConnection: IConnectionProfile) {
if (this.isValidKnoxConnection(newConnection) &&
this._hadoopConnection.connectionProfile.id !== '-1' &&
this._hadoopConnection.connectionProfile.id !== this._activeContexts.defaultConnection.id) {
private refreshConnections(newConnection: ConnectionProfile) {
if (this.isValidConnection(newConnection) &&
this._activeConnection.id !== '-1' &&
this._activeConnection.id !== this._activeContexts.defaultConnection.id) {
// 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.unshift(this._activeContexts.defaultConnection);
}
@@ -439,18 +458,15 @@ export class NotebookModel extends Disposable implements INotebookModel {
try {
let sessionManager = this.notebookManager.sessionManager;
if (sessionManager) {
let defaultKernel = SparkMagicContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo, this.notebookOptions.notificationService);
this._defaultKernel = defaultKernel;
if (!this._defaultKernel) {
this._defaultKernel = NotebookContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo);
}
this._clientSessions.forEach(clientSession => {
clientSession.statusChanged(async (session) => {
if (session && session.defaultKernelLoaded === true) {
this._kernelsChangedEmitter.fire(defaultKernel);
} else if (session && !session.defaultKernelLoaded) {
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
}
this._kernelsChangedEmitter.fire(session.kernel);
});
});
this.doChangeKernel(defaultKernel);
this.doChangeKernel(this._defaultKernel);
}
} catch (err) {
let msg = notebookUtils.getErrorMessage(err);
@@ -484,6 +500,15 @@ export class NotebookModel extends Disposable implements INotebookModel {
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 {
this._inErrorState = true;
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> {
try {
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();
this._clientSessions = undefined;
this._activeClientSession = undefined;
@@ -509,11 +539,13 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
private async loadActiveContexts(kernelChangedArgs: nb.IKernelChangedArgs): Promise<void> {
this._activeContexts = await SparkMagicContexts.getContextsForKernel(this.notebookOptions.connectionService, kernelChangedArgs, this.connectionProfile);
this._contextsChangedEmitter.fire();
if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.options !== undefined) {
let defaultHadoopConnection = new NotebookConnection(this.contexts.defaultConnection);
this.changeContext(defaultHadoopConnection.host);
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();
if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.serverName !== undefined) {
this.changeContext(this.contexts.defaultConnection.serverName);
}
}
}
@@ -555,6 +587,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
display_name: spec.display_name,
language: spec.language
};
this.clientSession.configureKernel(this._savedKernelInfo);
} catch (err) {
// 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
* @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++) {
if (this.notebookManagers[i].sessionManager && this.notebookManagers[i].sessionManager.specs && this.notebookManagers[i].sessionManager.specs.kernels) {
let index = this.notebookManagers[i].sessionManager.specs.kernels.findIndex(kernel => kernel.name === kernelSpec.name);
if (index >= 0) {
this._activeClientSession = this._clientSessions[i];
this._providerId = this.notebookManagers[i].providerId;
sessionManagerFound = true;
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.

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 { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
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(IViewletService) private viewletService: IViewletService,
@Inject(IUntitledEditorService) private untitledEditorService: IUntitledEditorService,
@Inject(IEditorGroupsService) private editorGroupService: IEditorGroupsService
@Inject(IEditorGroupsService) private editorGroupService: IEditorGroupsService,
@Inject(ICapabilitiesService) private capabilitiesService: ICapabilitiesService
) {
super();
this.updateProfile();
@@ -105,11 +107,11 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
// use global connection if possible
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
if (profile && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
if (profile && profile.providerName) {
this.profile = profile;
} else {
// 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) {
this.profile = profiles[0];
}
@@ -239,7 +241,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
connectionService: this.connectionManagementService,
notificationService: this.notificationService,
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);
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
await model.requestModelLoad(this._notebookParams.isTrusted);
@@ -324,7 +329,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
let attachToContainer = document.createElement('div');
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);
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 { 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 { 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 { 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';
const msgLoading = localize('loading', 'Loading kernels...');
@@ -26,7 +27,7 @@ const attachToLabel: string = localize('AttachTo', 'Attach to: ');
const msgLoadingContexts = localize('loadingContexts', 'Loading contexts...');
const msgAddNewConnection = localize('addNewConnection', 'Add new 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).
export class AddCellAction extends Action {
@@ -209,7 +210,8 @@ export class AttachToDropdown extends SelectBox {
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelRegistered: Promise<INotebookModel>,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@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);
if (modelRegistered) {
modelRegistered
@@ -219,8 +221,8 @@ export class AttachToDropdown extends SelectBox {
});
}
this.onDidSelect(e => {
let connection = this.model.contexts.otherConnections.find((c) => c.options.host === e.selected);
this.doChangeContext(connection);
let connection = this.model.contexts.otherConnections.find((c) => c.serverName === e.selected);
this.doChangeContext(new ConnectionProfile(this._capabilitiesService, connection));
});
}
@@ -228,54 +230,55 @@ export class AttachToDropdown extends SelectBox {
this.model = model;
model.contextsChanged(() => {
if (this.model.clientSession.kernel && this.model.clientSession.kernel.name) {
let currentKernel = this.model.clientSession.kernel.name;
this.loadAttachToDropdown(this.model, currentKernel);
let currentKernelSpec = this.model.specs.kernels.find(kernel => kernel.name === this.model.clientSession.kernel.name);
this.loadAttachToDropdown(this.model, currentKernelSpec.display_name);
}
});
}
// Load "Attach To" dropdown with the values corresponding to Kernel dropdown
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]);
}
else {
let hadoopConnections = this.getHadoopConnections(model);
let connections = this.getConnections(model);
this.enable();
if (hadoopConnections.length === 1 && hadoopConnections[0] === msgAddNewConnection) {
hadoopConnections.unshift(msgSelectConnection);
if (connections.length === 1 && connections[0] === msgAddNewConnection) {
connections.unshift(msgSelectConnection);
this.selectWithOptionName(msgSelectConnection);
}
else {
hadoopConnections.push(msgAddNewConnection);
connections.push(msgAddNewConnection);
}
this.setOptions(hadoopConnections);
this.setOptions(connections);
}
}
//Get hadoop connections from context
public getHadoopConnections(model: INotebookModel): string[] {
let otherHadoopConnections: IConnectionProfile[] = [];
model.contexts.otherConnections.forEach((conn) => { otherHadoopConnections.push(conn); });
this.selectWithOptionName(model.contexts.defaultConnection.options.host);
otherHadoopConnections = this.setHadoopConnectionsList(model.contexts.defaultConnection, model.contexts.otherConnections);
let hadoopConnections = otherHadoopConnections.map((context) => context.options.host);
return hadoopConnections;
//Get connections from context
public getConnections(model: INotebookModel): string[] {
let otherConnections: ConnectionProfile[] = [];
model.contexts.otherConnections.forEach((conn) => { otherConnections.push(conn); });
this.selectWithOptionName(model.contexts.defaultConnection.serverName);
otherConnections = this.setConnectionsList(model.contexts.defaultConnection, model.contexts.otherConnections);
let connections = otherConnections.map((context) => context.serverName);
return connections;
}
private setHadoopConnectionsList(defaultHadoopConnection: IConnectionProfile, otherHadoopConnections: IConnectionProfile[]) {
if (defaultHadoopConnection.options.host !== msgSelectConnection) {
otherHadoopConnections = otherHadoopConnections.filter(conn => conn.options.host !== defaultHadoopConnection.options.host);
otherHadoopConnections.unshift(defaultHadoopConnection);
if (otherHadoopConnections.length > 1) {
otherHadoopConnections = otherHadoopConnections.filter(val => val.options.host !== msgSelectConnection);
private setConnectionsList(defaultConnection: ConnectionProfile, otherConnections: ConnectionProfile[]) {
if (defaultConnection.serverName !== msgSelectConnection) {
otherConnections = otherConnections.filter(conn => conn.serverName !== defaultConnection.serverName);
otherConnections.unshift(defaultConnection);
if (otherConnections.length > 1) {
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) {
this.openConnectionDialog();
} else {
@@ -291,15 +294,15 @@ export class AttachToDropdown extends SelectBox {
**/
public async openConnectionDialog(): Promise<void> {
try {
//TODO: Figure out how to plumb through the correct provider here
await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: [notebookConstants.hadoopKnoxProviderName] }).then(connection => {
await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: this.model.getApplicableConnectionProviderIds(this.model.clientSession.kernel.name) }).then(connection => {
let attachToConnections = this.values;
if (!connection) {
this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name);
return;
}
let connectedServer = connection.options[notebookConstants.hostPropName];
//Check to see if the same host is already there in dropdown. We only have host names in dropdown
let connectionProfile = new ConnectionProfile(this._capabilitiesService, connection);
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)) {
this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name);
this.doChangeContext();
@@ -320,7 +323,7 @@ export class AttachToDropdown extends SelectBox {
this.select(index);
// Call doChangeContext to set the newly chosen connection in the model
this.doChangeContext(connection);
this.doChangeContext(connectionProfile);
});
}
catch (error) {

View File

@@ -13,7 +13,9 @@ import { Emitter, Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
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 { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
@@ -27,10 +29,13 @@ export class NotebookInputModel extends EditorModel {
private dirty: boolean;
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
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[]) {
super();
this.dirty = false;
this._providerId = provider;
this._standardKernels = [];
}
public get providerId(): string {
@@ -49,6 +54,28 @@ export class NotebookInputModel extends EditorModel {
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 {
return this._isTrusted;
}
@@ -116,6 +143,14 @@ export class NotebookInput extends EditorInput {
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 {
return NotebookInput.ID;
}

View File

@@ -58,7 +58,28 @@ export function getProvidersForFileName(fileName: string, notebookService: INote
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
export function sqlNotebooksEnabled() {
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;
color: var(--jp-ui-font-color1);
font-size: 12px;
table-layout: fixed;
table-layout: auto;
margin-left: auto;
margin-right: auto;
}
@@ -338,7 +338,7 @@ output-component .jp-RenderedHTMLCommon thead {
output-component .jp-RenderedHTMLCommon td,
output-component .jp-RenderedHTMLCommon th,
output-component .jp-RenderedHTMLCommon tr {
text-align: right;
text-align: left;
vertical-align: middle;
padding: 0.5em 0.5em;
line-height: normal;
@@ -370,6 +370,7 @@ output-component .jp-RenderedHTMLCommon tbody tr:hover {
output-component .jp-RenderedHTMLCommon table {
margin-bottom: 1em;
display: table-row;
}
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
*/
connectionId?: string;
/**
* Default kernel for notebook
*/
defaultKernel?: nb.IKernelSpec;
}
/**
@@ -1666,9 +1671,14 @@ declare module 'sqlops' {
*/
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
export interface IStandardKernel {
readonly name: string;
readonly connectionProviderIds: string[];
}
export interface NotebookProvider {
readonly providerId: string;
readonly standardKernels: string[];
readonly standardKernels: IStandardKernel[];
getNotebookManager(notebookUri: vscode.Uri): Thenable<NotebookManager>;
handleNotebookClosed(notebookUri: vscode.Uri): void;
}
@@ -1945,6 +1955,10 @@ declare module 'sqlops' {
defaultKernelLoaded?: boolean;
changeKernel(kernelInfo: IKernelSpec): Thenable<IKernel>;
configureKernel(kernelInfo: IKernelSpec): Thenable<void>;
configureConnection(connection: IConnectionProfile): Thenable<void>;
}
export interface ISessionOptions {

View File

@@ -125,6 +125,16 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
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> {
let kernel = this._getAdapter<sqlops.nb.IKernel>(kernelId);
return kernel.ready.then(success => kernel.info);

View File

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

View File

@@ -288,11 +288,30 @@ class SessionWrapper implements sqlops.nb.ISession {
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> {
let kernelDetails = await this._proxy.ext.$changeKernel(this.sessionDetails.sessionId, kernelInfo);
this._kernel = new KernelWrapper(this._proxy, kernelDetails);
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 {

View File

@@ -25,7 +25,7 @@ import {
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
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 { disposed } from 'vs/base/common/errors';
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
@@ -332,6 +332,11 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
}
model.providers = providers;
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 editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));

View File

@@ -773,6 +773,8 @@ export interface ExtHostNotebookShape {
// Session APIs
$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
$getKernelReadyStatus(kernelId: number): Thenable<sqlops.nb.IInfoReply>;
@@ -829,6 +831,7 @@ export interface INotebookShowOptions {
preview?: boolean;
providerId?: string;
connectionId?: string;
defaultKernel?: sqlops.nb.IKernelSpec;
}
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 { localize } from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform';
import * as sqlops from 'sqlops';
import { Event, Emitter } from 'vs/base/common/event';
export const Extensions = {
@@ -18,7 +19,7 @@ export const Extensions = {
export interface NotebookProviderRegistration {
provider: string;
fileExtensions: string | string[];
standardKernels: string | string[];
standardKernels: sqlops.nb.IStandardKernel | sqlops.nb.IStandardKernel[];
}
let notebookProviderType: IJSONSchema = {
@@ -44,11 +45,38 @@ let notebookProviderType: IJSONSchema = {
standardKernels: {
description: localize('carbon.extension.contributes.notebook.standardKernels', 'What kernels should be standard with this notebook provider'),
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: {
name: {
type: 'string',
},
connectionProviderIds: {
type: 'array',
items: {
type: 'string'
}
}
}
},
{
type: 'array',
items: {
type: 'string'
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[];
getStandardKernelsForProvider(provider: string): sqlops.nb.IStandardKernel[];
/**
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
* run cells in a notebook.

View File

@@ -80,6 +80,7 @@ export class NotebookService extends Disposable implements INotebookService {
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
private _editors = new Map<string, INotebookEditor>();
private _fileToProviders = new Map<string, NotebookProviderRegistration[]>();
private _providerToStandardKernels = new Map<string, nb.IStandardKernel[]>();
private _registrationComplete = new Deferred<void>();
private _isRegistrationComplete = false;
@@ -121,6 +122,9 @@ export class NotebookService extends Disposable implements INotebookService {
this.addFileProvider(registration.fileExtensions, registration);
}
}
if (registration.standardKernels) {
this.addStandardKernels(registration);
}
}
registerProvider(providerId: string, instance: INotebookProvider): void {
@@ -154,6 +158,26 @@ export class NotebookService extends Disposable implements INotebookService {
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[] {
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;
}
getStandardKernelsForProvider(provider: string): nb.IStandardKernel[] {
return this._providerToStandardKernels.get(provider.toUpperCase());
}
public shutdown(): void {
this._managersMap.forEach(manager => {
manager.forEach(m => {
@@ -337,7 +365,7 @@ export class NotebookService extends Disposable implements INotebookService {
notebookRegistry.registerNotebookProvider({
provider: sqlProvider.providerId,
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 {
return this._sessionManager;
}
}

View File

@@ -7,6 +7,7 @@
import { nb } from 'sqlops';
import { localize } from 'vs/nls';
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
export const noKernel: string = localize('noKernel', 'No Kernel');
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> {
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 {

View File

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