From f44ae6ab959ac562af8440d0ac5b648b60e79b41 Mon Sep 17 00:00:00 2001 From: Kyle Kavanagh Date: Sat, 17 Aug 2019 13:28:57 -0500 Subject: [PATCH 01/13] Working kernel selection when session created --- .../datascience/jupyter/jupyterSession.ts | 72 +++++++++++++++++-- .../jupyter/jupyterSessionManager.ts | 14 ++-- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index 515739ed95df..1e264333b6fd 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -14,7 +14,7 @@ import { JSONObject } from '@phosphor/coreutils'; import { Slot } from '@phosphor/signaling'; import { Agent as HttpsAgent } from 'https'; import * as uuid from 'uuid/v4'; -import { Event, EventEmitter } from 'vscode'; +import { Event, EventEmitter, QuickPickItem } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import { Cancellation } from '../../common/cancellation'; @@ -33,6 +33,11 @@ import { import { JupyterKernelPromiseFailedError } from './jupyterKernelPromiseFailedError'; import { JupyterWaitForIdleError } from './jupyterWaitForIdleError'; import { createJupyterWebSocket } from './jupyterWebSocket'; +import { IApplicationShell } from '../../common/application/types'; + +interface KernelQuickPickItem extends QuickPickItem { + kernelId: string; +} export class JupyterSession implements IJupyterSession { private connInfo: IConnection | undefined; @@ -47,12 +52,17 @@ export class JupyterSession implements IJupyterSession { private connected: boolean = false; private jupyterPasswordConnect: IJupyterPasswordConnect; private oldSessions: Session.ISession[] = []; + private readonly appShell: IApplicationShell; + private allowShutdown: boolean = true; constructor( + appShell: IApplicationShell, connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, jupyterPasswordConnect: IJupyterPasswordConnect ) { + console.trace() + this.appShell = appShell; this.connInfo = connInfo; this.kernelSpec = kernelSpec; this.jupyterPasswordConnect = jupyterPasswordConnect; @@ -154,6 +164,7 @@ export class JupyterSession implements IJupyterSession { this.sessionManager = new SessionManager({ serverSettings: serverSettings }); this.contentsManager = new ContentsManager({ serverSettings: serverSettings }); + // Start a new session this.session = await this.createSession(serverSettings, this.contentsManager, cancelToken); @@ -217,12 +228,45 @@ export class JupyterSession implements IJupyterSession { throw exception; } + private async createSession(serverSettings: ServerConnection.ISettings, contentsManager: ContentsManager, cancelToken?: CancellationToken): Promise { // Create a temporary notebook for this session. this.notebookFiles.push(await contentsManager.newUntitled({ type: 'notebook' })); - // Create our session options using this temporary notebook and our connection info + const runningKernels: Kernel.IModel[] = await Kernel.listRunning(serverSettings); + const arr: KernelQuickPickItem[] = runningKernels.map(runningKernel => { + traceInfo(`Found running kernel ${runningKernel.id}, running since ${runningKernel.last_activity}`); + const localLastActivity = runningKernel.last_activity ? new Date(runningKernel.last_activity.toString()).toLocaleString() : '?'; + return { + label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, + detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, + kernelId: runningKernel.id + }; + }); + const startNewKernel = { + label: 'Start new kernel on Jupyter server', + picked: true, + kernelId: 'none' + }; + arr.unshift(startNewKernel); + + const kernelSelection = await this.appShell.showQuickPick(arr, { + ignoreFocusOut: true, + placeHolder: 'Select Jupyer Kernel' + }); + + const autoShutdown = { + label: 'Automatically shutdown Kernel when closed', + picked: true + }; + const leaveRunning = { + label: 'Leave Kernel running when closed', + picked: true + }; + const shutdownOptions = [autoShutdown, leaveRunning]; + const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); + const options: Session.IOptions = { path: this.notebookFiles[this.notebookFiles.length - 1].path, kernelName: this.kernelSpec ? this.kernelSpec.name : '', @@ -230,6 +274,20 @@ export class JupyterSession implements IJupyterSession { serverSettings: serverSettings }; + if (kernelSelection && kernelSelection !== startNewKernel) { + options.kernelId = kernelSelection.kernelId + traceInfo(`Connecting to existing kernel ${kernelSelection.kernelId}`); + } else { + traceInfo('Creating new kernel for connection'); + } + + if (shutdownSelection === leaveRunning) { + this.allowShutdown = false; + traceInfo('Session will not be shutdown on close'); + } + + // Create our session options using this temporary notebook and our connection info + return Cancellation.race(() => this.sessionManager!.startNew(options), cancelToken); } @@ -347,10 +405,14 @@ export class JupyterSession implements IJupyterSession { }); } } - await waitForPromise(session.shutdown(), 1000); + if (this.allowShutdown) { + await waitForPromise(session.shutdown(), 1000); + } } else { - // Shutdown may fail if the process has been killed - await waitForPromise(session.shutdown(), 1000); + if (this.allowShutdown) { + // Shutdown may fail if the process has been killed + await waitForPromise(session.shutdown(), 1000); + } } } catch { noop(); diff --git a/src/client/datascience/jupyter/jupyterSessionManager.ts b/src/client/datascience/jupyter/jupyterSessionManager.ts index b36053bb1418..025e345cc33c 100644 --- a/src/client/datascience/jupyter/jupyterSessionManager.ts +++ b/src/client/datascience/jupyter/jupyterSessionManager.ts @@ -6,18 +6,20 @@ import { inject, injectable } from 'inversify'; import { CancellationToken } from 'vscode-jsonrpc'; import { IConnection, IJupyterKernelSpec, IJupyterPasswordConnect, IJupyterSession, IJupyterSessionManager } from '../types'; +import { IApplicationShell } from '../../common/application/types'; import { JupyterKernelSpec } from './jupyterKernelSpec'; import { JupyterSession } from './jupyterSession'; @injectable() export class JupyterSessionManager implements IJupyterSessionManager { constructor( - @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect - ) {} + @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect, + @inject(IApplicationShell) private appShell: IApplicationShell + ) { } - public async startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken) : Promise { + public async startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise { // Create a new session and attempt to connect to it - const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect); + const session = new JupyterSession(this.appShell, connInfo, kernelSpec, this.jupyterPasswordConnect); try { await session.connect(cancelToken); } finally { @@ -28,8 +30,8 @@ export class JupyterSessionManager implements IJupyterSessionManager { return session; } - public async getActiveKernelSpecs(connection: IConnection) : Promise { - let sessionManager: SessionManager | undefined ; + public async getActiveKernelSpecs(connection: IConnection): Promise { + let sessionManager: SessionManager | undefined; try { // Use our connection to create a session manager const serverSettings = ServerConnection.makeSettings( From 509e50bf41837f7f15affe6fe8d5b0ced822508b Mon Sep 17 00:00:00 2001 From: Kyle Kavanagh Date: Sat, 17 Aug 2019 15:12:22 -0500 Subject: [PATCH 02/13] Select jupyter kernel once up front --- package.json | 14 ++++ src/client/common/types.ts | 2 + src/client/datascience/constants.ts | 2 + src/client/datascience/datascience.ts | 66 +++++++++++++++++-- .../datascience/jupyter/jupyterExecution.ts | 6 +- .../datascience/jupyter/jupyterSession.ts | 64 ++++-------------- .../jupyter/jupyterSessionManager.ts | 38 +++++++---- .../liveshare/guestJupyterSessionManager.ts | 12 +++- src/client/datascience/types.ts | 1 + src/client/telemetry/index.ts | 2 + .../react-common/settingsReactSide.ts | 2 + src/test/datascience/color.test.ts | 2 + .../datascience/dataScienceIocContainer.ts | 2 + .../codewatcher.unit.test.ts | 2 + src/test/datascience/execution.unit.test.ts | 2 + ...eractiveWindowCommandListener.unit.test.ts | 2 + .../interactiveWindowTestHelpers.tsx | 2 + src/test/datascience/mockJupyterManager.ts | 5 ++ src/test/datascience/reactHelpers.ts | 2 + 19 files changed, 152 insertions(+), 76 deletions(-) diff --git a/package.json b/package.json index c022da8be5d3..b74aa90d38c0 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,8 @@ "onCommand:python.datascience.showhistorypane", "onCommand:python.datascience.importnotebook", "onCommand:python.datascience.selectjupyteruri", + "onCommand:python.dataScience.jupyterserverkernelid", + "onCommand:python.dataScience.jupyterserverallowkernelshutdown", "onCommand:python.datascience.exportfileasnotebook", "onCommand:python.datascience.exportfileandoutputasnotebook", "onCommand:python.enableSourceMapSupport" @@ -1365,6 +1367,18 @@ "description": "Select the Jupyter server URI to connect to. Select 'local' to launch a new Juypter server on the local machine.", "scope": "resource" }, + "python.dataScience.jupyterServerKernelId": { + "type": "string", + "default": "", + "description": "Select the Jupyter server Kernel UUID to connect to. Leave blank to start a new kernel", + "scope": "resource" + }, + "python.dataScience.jupyterServerAllowKernelShutdown": { + "type": "boolean", + "default": true, + "description": "Shutdown the Jupyter kernel when finishede", + "scope": "resource" + }, "python.dataScience.notebookFileRoot": { "type": "string", "default": "${workspaceFolder}", diff --git a/src/client/common/types.ts b/src/client/common/types.ts index bf2354d29f9f..0e223ca96efc 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -303,6 +303,8 @@ export interface IDataScienceSettings { jupyterInterruptTimeout: number; jupyterLaunchTimeout: number; jupyterLaunchRetries: number; + jupyterServerAllowKernelShutdown: boolean, + jupyterServerKernelId: string, jupyterServerURI: string; notebookFileRoot: string; changeDirOnImportExport: boolean; diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index 216f37fea021..c9bc41936473 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -112,6 +112,8 @@ export enum Telemetry { ExpandAll = 'DATASCIENCE.EXPAND_ALL', CollapseAll = 'DATASCIENCE.COLLAPSE_ALL', SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI', + JupyterKernelSpecified = 'DATASCIENCE.JUPYTER_KERNEL_SPECIFIED', + JupyterKernelAutoShutdown = 'DATASCIENCE.JUPYTER_AUTO_SHUTDOWN', SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL', SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED', Interrupt = 'DATASCIENCE.INTERRUPT', diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index 55e5579ad43a..e6e93001674b 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. 'use strict'; import '../common/extensions'; - +import { Kernel } from '@jupyterlab/services'; import { JSONObject } from '@phosphor/coreutils'; import { inject, injectable } from 'inversify'; import { URL } from 'url'; @@ -11,7 +11,7 @@ import * as vscode from 'vscode'; import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, IWorkspaceService } from '../common/application/types'; import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants'; import { ContextKey } from '../common/contextKey'; -import { traceError } from '../common/logger'; +import { traceInfo, traceError } from '../common/logger'; import { BANNER_NAME_DS_SURVEY, IConfigurationService, @@ -26,7 +26,12 @@ import { IServiceContainer } from '../ioc/types'; import { captureTelemetry, sendTelemetryEvent } from '../telemetry'; import { hasCells } from './cellFactory'; import { Commands, EditorContexts, Settings, Telemetry } from './constants'; -import { ICodeWatcher, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener } from './types'; +import { JupyterExecutionBase } from './jupyter/jupyterExecution'; +import { ICodeWatcher, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener, IJupyterSessionManager } from './types'; + +interface IKernelQuickPickItem extends vscode.QuickPickItem { + kernelId: string; +} @injectable() export class DataScience implements IDataScience { @@ -44,7 +49,8 @@ export class DataScience implements IDataScience { @inject(IDocumentManager) private documentManager: IDocumentManager, @inject(IApplicationShell) private appShell: IApplicationShell, @inject(IDebugService) private debugService: IDebugService, - @inject(IWorkspaceService) private workspace: IWorkspaceService + @inject(IWorkspaceService) private workspace: IWorkspaceService, + @inject(IJupyterSessionManager) private sessionManager: IJupyterSessionManager ) { this.commandListeners = this.serviceContainer.getAll(IDataScienceCommandListener); this.dataScienceSurveyBanner = this.serviceContainer.get(IPythonExtensionBanner, BANNER_NAME_DS_SURVEY); @@ -289,6 +295,58 @@ export class DataScience implements IDataScience { if (userURI) { await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); + + const connInfo = JupyterExecutionBase.createRemoteConnectionInfo(userURI, this.configuration); + + const runningKernels: Kernel.IModel[] = await this.sessionManager.getActiveKernels(connInfo); + const arr: IKernelQuickPickItem[] = runningKernels.map(runningKernel => { + traceInfo(`Found running kernel ${runningKernel.id}, running since ${runningKernel.last_activity}`); + const localLastActivity = runningKernel.last_activity ? new Date(runningKernel.last_activity.toString()).toLocaleString() : '?'; + return { + label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, + detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, + kernelId: runningKernel.id + }; + }); + const startNewKernel = { + label: 'Start new kernel on Jupyter server', + picked: true, + kernelId: 'none' + }; + arr.unshift(startNewKernel); + + const kernelSelection = await this.appShell.showQuickPick(arr, { + ignoreFocusOut: true, + placeHolder: 'Select Jupyer Kernel' + }); + + const autoShutdown = { + label: 'Automatically shutdown Kernel when closed', + picked: true + }; + const leaveRunning = { + label: 'Leave Kernel running when closed', + picked: true + }; + const shutdownOptions = [autoShutdown, leaveRunning]; + const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); + + if (kernelSelection && kernelSelection !== startNewKernel) { + traceInfo(`Connecting to existing kernel ${kernelSelection.kernelId}`); + sendTelemetryEvent(Telemetry.JupyterKernelSpecified); + await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelSelection.kernelId, undefined, vscode.ConfigurationTarget.Workspace); + } else { + traceInfo('Creating new kernel for connection'); + } + + let allowShutdown = true; + if (shutdownSelection === leaveRunning) { + traceInfo('Session will not be shutdown on close'); + allowShutdown = false; + } + sendTelemetryEvent(Telemetry.JupyterKernelAutoShutdown, undefined, { autoShutdownEnabled: allowShutdown }); + await this.configuration.updateSetting('dataScience.jupyterServerAllowKernelShutdown', allowShutdown, undefined, vscode.ConfigurationTarget.Workspace); + } } diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 4bf699a9167a..6d4e308f1061 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -291,7 +291,7 @@ export class JupyterExecutionBase implements IJupyterExecution { } } else { // If we have a URI spec up a connection info for it - connection = this.createRemoteConnectionInfo(options.uri); + connection = JupyterExecutionBase.createRemoteConnectionInfo(options.uri, this.configuration); kernelSpec = undefined; } @@ -310,7 +310,7 @@ export class JupyterExecutionBase implements IJupyterExecution { return { connection, kernelSpec }; } - private createRemoteConnectionInfo = (uri: string): IConnection => { + public static createRemoteConnectionInfo = (uri: string, configuration: IConfigurationService): IConnection => { let url: URL; try { url = new URL(uri); @@ -318,7 +318,7 @@ export class JupyterExecutionBase implements IJupyterExecution { // This should already have been parsed when set, so just throw if it's not right here throw err; } - const settings = this.configuration.getSettings(); + const settings = configuration.getSettings(); const allowUnauthorized = settings.datascience.allowUnauthorizedRemoteConnection ? settings.datascience.allowUnauthorizedRemoteConnection : false; return { diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index 1e264333b6fd..395d19115450 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -14,7 +14,7 @@ import { JSONObject } from '@phosphor/coreutils'; import { Slot } from '@phosphor/signaling'; import { Agent as HttpsAgent } from 'https'; import * as uuid from 'uuid/v4'; -import { Event, EventEmitter, QuickPickItem } from 'vscode'; +import { Event, EventEmitter } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import { Cancellation } from '../../common/cancellation'; @@ -33,11 +33,6 @@ import { import { JupyterKernelPromiseFailedError } from './jupyterKernelPromiseFailedError'; import { JupyterWaitForIdleError } from './jupyterWaitForIdleError'; import { createJupyterWebSocket } from './jupyterWebSocket'; -import { IApplicationShell } from '../../common/application/types'; - -interface KernelQuickPickItem extends QuickPickItem { - kernelId: string; -} export class JupyterSession implements IJupyterSession { private connInfo: IConnection | undefined; @@ -52,20 +47,21 @@ export class JupyterSession implements IJupyterSession { private connected: boolean = false; private jupyterPasswordConnect: IJupyterPasswordConnect; private oldSessions: Session.ISession[] = []; - private readonly appShell: IApplicationShell; - private allowShutdown: boolean = true; + private allowShutdown: boolean; + private desiredKernelId: string | undefined; constructor( - appShell: IApplicationShell, connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, - jupyterPasswordConnect: IJupyterPasswordConnect + jupyterPasswordConnect: IJupyterPasswordConnect, + desiredKernelId: string | undefined, + allowShutdown: boolean ) { - console.trace() - this.appShell = appShell; this.connInfo = connInfo; this.kernelSpec = kernelSpec; this.jupyterPasswordConnect = jupyterPasswordConnect; + this.allowShutdown = allowShutdown; + this.desiredKernelId = desiredKernelId; } public dispose(): Promise { @@ -234,39 +230,6 @@ export class JupyterSession implements IJupyterSession { // Create a temporary notebook for this session. this.notebookFiles.push(await contentsManager.newUntitled({ type: 'notebook' })); - const runningKernels: Kernel.IModel[] = await Kernel.listRunning(serverSettings); - const arr: KernelQuickPickItem[] = runningKernels.map(runningKernel => { - traceInfo(`Found running kernel ${runningKernel.id}, running since ${runningKernel.last_activity}`); - const localLastActivity = runningKernel.last_activity ? new Date(runningKernel.last_activity.toString()).toLocaleString() : '?'; - return { - label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, - detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, - kernelId: runningKernel.id - }; - }); - const startNewKernel = { - label: 'Start new kernel on Jupyter server', - picked: true, - kernelId: 'none' - }; - arr.unshift(startNewKernel); - - const kernelSelection = await this.appShell.showQuickPick(arr, { - ignoreFocusOut: true, - placeHolder: 'Select Jupyer Kernel' - }); - - const autoShutdown = { - label: 'Automatically shutdown Kernel when closed', - picked: true - }; - const leaveRunning = { - label: 'Leave Kernel running when closed', - picked: true - }; - const shutdownOptions = [autoShutdown, leaveRunning]; - const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); - const options: Session.IOptions = { path: this.notebookFiles[this.notebookFiles.length - 1].path, kernelName: this.kernelSpec ? this.kernelSpec.name : '', @@ -274,18 +237,13 @@ export class JupyterSession implements IJupyterSession { serverSettings: serverSettings }; - if (kernelSelection && kernelSelection !== startNewKernel) { - options.kernelId = kernelSelection.kernelId - traceInfo(`Connecting to existing kernel ${kernelSelection.kernelId}`); + if (this.desiredKernelId) { + options.kernelId = this.desiredKernelId; + traceInfo(`Connecting to existing kernel ${this.desiredKernelId}`); } else { traceInfo('Creating new kernel for connection'); } - if (shutdownSelection === leaveRunning) { - this.allowShutdown = false; - traceInfo('Session will not be shutdown on close'); - } - // Create our session options using this temporary notebook and our connection info return Cancellation.race(() => this.sessionManager!.startNew(options), cancelToken); diff --git a/src/client/datascience/jupyter/jupyterSessionManager.ts b/src/client/datascience/jupyter/jupyterSessionManager.ts index 025e345cc33c..719860d238d7 100644 --- a/src/client/datascience/jupyter/jupyterSessionManager.ts +++ b/src/client/datascience/jupyter/jupyterSessionManager.ts @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { ServerConnection, SessionManager } from '@jupyterlab/services'; +import { ServerConnection, SessionManager, Kernel } from '@jupyterlab/services'; import { inject, injectable } from 'inversify'; import { CancellationToken } from 'vscode-jsonrpc'; import { IConnection, IJupyterKernelSpec, IJupyterPasswordConnect, IJupyterSession, IJupyterSessionManager } from '../types'; -import { IApplicationShell } from '../../common/application/types'; +import { IConfigurationService } from '../../common/types'; import { JupyterKernelSpec } from './jupyterKernelSpec'; import { JupyterSession } from './jupyterSession'; @@ -14,12 +14,15 @@ import { JupyterSession } from './jupyterSession'; export class JupyterSessionManager implements IJupyterSessionManager { constructor( @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect, - @inject(IApplicationShell) private appShell: IApplicationShell + @inject(IConfigurationService) private readonly configurationService: IConfigurationService ) { } public async startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise { // Create a new session and attempt to connect to it - const session = new JupyterSession(this.appShell, connInfo, kernelSpec, this.jupyterPasswordConnect); + const settings = this.configurationService.getSettings(); + const allowShutdown = settings.datascience.jupyterServerAllowKernelShutdown; + const kernelId = settings.datascience.jupyterServerKernelId; + const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect, kernelId, allowShutdown); try { await session.connect(cancelToken); } finally { @@ -30,19 +33,15 @@ export class JupyterSessionManager implements IJupyterSessionManager { return session; } + public getActiveKernels(connection: IConnection): Promise { + return Kernel.listRunning(this.makeServerSettings(connection)); + } + public async getActiveKernelSpecs(connection: IConnection): Promise { let sessionManager: SessionManager | undefined; try { // Use our connection to create a session manager - const serverSettings = ServerConnection.makeSettings( - { - baseUrl: connection.baseUrl, - token: connection.token, - pageUrl: '', - // A web socket is required to allow token authentication (what if there is no token authentication?) - wsUrl: connection.baseUrl.replace('http', 'ws'), - init: { cache: 'no-store', credentials: 'same-origin' } - }); + const serverSettings = this.makeServerSettings(connection); sessionManager = new SessionManager({ serverSettings: serverSettings }); // Ask the session manager to refresh its list of kernel specs. @@ -67,4 +66,17 @@ export class JupyterSessionManager implements IJupyterSessionManager { } + + private makeServerSettings(connection: IConnection): ServerConnection.ISettings { + return ServerConnection.makeSettings( + { + baseUrl: connection.baseUrl, + token: connection.token, + pageUrl: '', + // A web socket is required to allow token authentication (what if there is no token authentication?) + wsUrl: connection.baseUrl.replace('http', 'ws'), + init: { cache: 'no-store', credentials: 'same-origin' } + }); + } + } diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts b/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts index 517eaa655569..9f2361142150 100644 --- a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts +++ b/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts @@ -3,20 +3,26 @@ 'use strict'; import { CancellationToken } from 'vscode-jsonrpc'; +import { Kernel } from '@jupyterlab/services'; import { noop } from '../../../../test/core'; import { IConnection, IJupyterKernelSpec, IJupyterSession, IJupyterSessionManager } from '../../types'; export class GuestJupyterSessionManager implements IJupyterSessionManager { - public constructor(private realSessionManager : IJupyterSessionManager) { + public constructor(private realSessionManager: IJupyterSessionManager) { noop(); } - public startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken) : Promise { + public startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise { return this.realSessionManager.startNew(connInfo, kernelSpec, cancelToken); } - public async getActiveKernelSpecs(_connection: IConnection) : Promise { + public async getActiveKernels(_connection: IConnection): Promise { + // Don't return any kernels in guest mode. They're only needed for the host side + return Promise.resolve([]); + } + + public async getActiveKernelSpecs(_connection: IConnection): Promise { // Don't return any kernel specs in guest mode. They're only needed for the host side return Promise.resolve([]); } diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 14591afabafe..93207aa513b6 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -148,6 +148,7 @@ export const IJupyterSessionManager = Symbol('IJupyterSessionManager'); export interface IJupyterSessionManager { startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise; getActiveKernelSpecs(connInfo: IConnection): Promise; + getActiveKernels(connInfo: IConnection): Promise; } export interface IJupyterKernelSpec extends IAsyncDisposable { diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 9e70f61d7c9d..f3ca42733a03 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1272,6 +1272,8 @@ export interface IEventNamePropertyMapping { [Telemetry.SelfCertsMessageClose]: never | undefined; [Telemetry.SelfCertsMessageEnabled]: never | undefined; [Telemetry.SelectJupyterURI]: never | undefined; + [Telemetry.JupyterKernelSpecified]: never | undefined; + [Telemetry.JupyterKernelAutoShutdown]: { autoShutdownEnabled: boolean }; [Telemetry.SetJupyterURIToLocal]: never | undefined; [Telemetry.SetJupyterURIToUserSpecified]: never | undefined; [Telemetry.ShiftEnterBannerShown]: never | undefined; diff --git a/src/datascience-ui/react-common/settingsReactSide.ts b/src/datascience-ui/react-common/settingsReactSide.ts index e7c81bff10c6..35f269cd73fa 100644 --- a/src/datascience-ui/react-common/settingsReactSide.ts +++ b/src/datascience-ui/react-common/settingsReactSide.ts @@ -36,6 +36,8 @@ function load() { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/color.test.ts b/src/test/datascience/color.test.ts index 1088dd868f46..6638a62d5616 100644 --- a/src/test/datascience/color.test.ts +++ b/src/test/datascience/color.test.ts @@ -53,6 +53,8 @@ suite('Theme colors', () => { jupyterLaunchTimeout: 20000, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 4aec10054698..02da59ba6710 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -407,6 +407,8 @@ export class DataScienceIocContainer extends UnitTestIocContainer { jupyterLaunchTimeout: 20000, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index 716246c00b1c..c9efadb5f096 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -74,6 +74,8 @@ suite('DataScience Code Watcher Unit Tests', () => { jupyterLaunchTimeout: 20000, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index c4c3d60738c3..b8e3ad409208 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -540,6 +540,8 @@ suite('Jupyter Execution', async () => { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts index 76653d3b7e34..9caa0a36faae 100644 --- a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts +++ b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts @@ -133,6 +133,8 @@ suite('Interactive window command listener', async () => { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: '', changeDirOnImportExport: true, notebookFileRoot: 'WORKSPACE', diff --git a/src/test/datascience/interactiveWindowTestHelpers.tsx b/src/test/datascience/interactiveWindowTestHelpers.tsx index c018277e6091..b95fa55c5e17 100644 --- a/src/test/datascience/interactiveWindowTestHelpers.tsx +++ b/src/test/datascience/interactiveWindowTestHelpers.tsx @@ -307,6 +307,8 @@ export function defaultDataScienceSettings(): IDataScienceSettings { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index a8b967665899..c43a832dc575 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -33,6 +33,7 @@ import { noop, sleep } from '../core'; import { MockJupyterSession } from './mockJupyterSession'; import { MockProcessService } from './mockProcessService'; import { MockPythonService } from './mockPythonService'; +import { Kernel } from '@jupyterlab/services'; // tslint:disable:no-any no-http-string no-multiline-string max-func-body-length @@ -236,6 +237,10 @@ export class MockJupyterManager implements IJupyterSessionManager { } } + public getActiveKernels(_connection: IConnection): Promise { + return Promise.resolve([]); + } + public getActiveKernelSpecs(_connection: IConnection): Promise { return Promise.resolve([]); } diff --git a/src/test/datascience/reactHelpers.ts b/src/test/datascience/reactHelpers.ts index c4152c58dc6d..5d6bc7b4ef2b 100644 --- a/src/test/datascience/reactHelpers.ts +++ b/src/test/datascience/reactHelpers.ts @@ -349,6 +349,8 @@ export function setUpDomEnvironment() { jupyterLaunchTimeout: 10, jupyterLaunchRetries: 3, enabled: true, + jupyterServerAllowKernelShutdown: true, + jupyterServerKernelId: '', jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, From f915eb5e25d4847bdb37639995e3111ff2d8588b Mon Sep 17 00:00:00 2001 From: Kyle Kavanagh Date: Sat, 17 Aug 2019 21:23:43 -0500 Subject: [PATCH 03/13] Rename temp file created when initially connecting to jupyter to avoid nasty jupyter session behavior --- src/client/datascience/datascience.ts | 9 ++++++--- src/client/datascience/jupyter/jupyterSession.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index e6e93001674b..dc630fb1957c 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -331,13 +331,16 @@ export class DataScience implements IDataScience { const shutdownOptions = [autoShutdown, leaveRunning]; const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); + let kernelUUID: string = ''; if (kernelSelection && kernelSelection !== startNewKernel) { - traceInfo(`Connecting to existing kernel ${kernelSelection.kernelId}`); + traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); + kernelUUID = kernelSelection.kernelId; sendTelemetryEvent(Telemetry.JupyterKernelSpecified); - await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelSelection.kernelId, undefined, vscode.ConfigurationTarget.Workspace); + } else { - traceInfo('Creating new kernel for connection'); + traceInfo('Will create a new kernel for connection'); } + await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelUUID, undefined, vscode.ConfigurationTarget.Workspace); let allowShutdown = true; if (shutdownSelection === leaveRunning) { diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index 395d19115450..bfaf2993bbc0 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -227,13 +227,17 @@ export class JupyterSession implements IJupyterSession { private async createSession(serverSettings: ServerConnection.ISettings, contentsManager: ContentsManager, cancelToken?: CancellationToken): Promise { - // Create a temporary notebook for this session. - this.notebookFiles.push(await contentsManager.newUntitled({ type: 'notebook' })); + // Create a temporary notebook for this session. Give it a unique name, so there is no possiblity of us reusing a session lingering the jupyter server + // (sessions in jupyter are indexed by path, regardless if the file exists of not and regardless of the session name, so deleting the file later doesn't save us) + // See https://github.com/jupyter/notebook/blob/5.7.x/notebook/services/sessions/handlers.py#L65 + const sessionUUID = uuid(); + const nbFile = await contentsManager.newUntitled({ type: 'notebook' }); + this.notebookFiles.push(await contentsManager.rename(nbFile.path, `.vscode-jupyter-session-${sessionUUID}.ipynb`)); const options: Session.IOptions = { path: this.notebookFiles[this.notebookFiles.length - 1].path, kernelName: this.kernelSpec ? this.kernelSpec.name : '', - name: uuid(), // This is crucial to distinguish this session from any other. + name: sessionUUID, // This is crucial to distinguish this session from any other. serverSettings: serverSettings }; From bad21401508880fa94700ec1dd0beafdad6f4aba Mon Sep 17 00:00:00 2001 From: "Kyle D. Kavanagh" Date: Sun, 18 Aug 2019 11:31:27 -0500 Subject: [PATCH 04/13] update news --- news/1 Enhancements/7014.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/7014.md diff --git a/news/1 Enhancements/7014.md b/news/1 Enhancements/7014.md new file mode 100644 index 000000000000..e0b01c98da37 --- /dev/null +++ b/news/1 Enhancements/7014.md @@ -0,0 +1 @@ +Add ability to reconnect to existing Jupyter kernel on remote jupyter hosts - @kdkavanagh From 17f8f619c9b0681d2b04babe6c919fe39e0a96a2 Mon Sep 17 00:00:00 2001 From: "Kyle D. Kavanagh" Date: Sun, 18 Aug 2019 11:32:53 -0500 Subject: [PATCH 05/13] fix typo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b74aa90d38c0..526f8a0ac77a 100644 --- a/package.json +++ b/package.json @@ -1376,7 +1376,7 @@ "python.dataScience.jupyterServerAllowKernelShutdown": { "type": "boolean", "default": true, - "description": "Shutdown the Jupyter kernel when finishede", + "description": "Shutdown the Jupyter kernel when finished", "scope": "resource" }, "python.dataScience.notebookFileRoot": { From ea2ec275eb360371490b50b3989a811acd18b2a9 Mon Sep 17 00:00:00 2001 From: Kyle Kavanagh Date: Sun, 18 Aug 2019 11:41:07 -0500 Subject: [PATCH 06/13] Add localization --- package.nls.json | 4 ++++ src/client/common/utils/localize.ts | 4 ++++ src/client/datascience/datascience.ts | 8 ++++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.nls.json b/package.nls.json index 1d794158e855..a92fd9f2b97a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -174,6 +174,10 @@ "DataScience.jupyterSelectURISpecifyURI": "Type in the URI to connect to a running Jupyter server", "DataScience.jupyterSelectURIPrompt": "Enter the URI of a Jupyter server", "DataScience.jupyterSelectURIInvalidURI": "Invalid URI specified", + "DataScience.jupyterServerReconnectKernelLocal": "Select Jupyer Kernel", + "DataScience.jupyterServerReconnectKernelStartNewLocal": "Start new kernel on Jupyter server", + "DataScience.jupyterServerKernelAutoShutdownLocal": "Automatically shutdown Kernel when closed", + "DataScience.jupyterServerKernelLeaveRunningLocal": "Leave Kernel running when closed", "DataScience.jupyterSelectPasswordPrompt": "Enter your notebook password", "DataScience.jupyterNotebookFailure": "Jupyter notebook failed to launch. \r\n{0}", "DataScience.jupyterNotebookConnectFailed": "Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}", diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index cf28e8d0244f..8d6f8958d03d 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -176,6 +176,10 @@ export namespace DataScience { export const jupyterSelectURISpecifyURI = localize('DataScience.jupyterSelectURISpecifyURI', 'Type in the URI for the Jupyter server'); export const jupyterSelectURIPrompt = localize('DataScience.jupyterSelectURIPrompt', 'Enter the URI of a Jupyter server'); export const jupyterSelectURIInvalidURI = localize('DataScience.jupyterSelectURIInvalidURI', 'Invalid URI specified'); + export const jupyterServerReconnectKernelLocal = localize('DataScience.jupyterServerReconnectKernelLocal', 'Select Jupyer Kernel'); + export const jupyterServerReconnectKernelStartNewLocal = localize('DataScience.jupyterServerReconnectKernelStartNewLocal', 'Start new kernel on Jupyter server'); + export const jupyterServerKernelAutoShutdownLocal = localize('DataScience.jupyterServerKernelAutoShutdownLocal', 'Automatically shutdown Kernel when closed'); + export const jupyterServerKernelLeaveRunningLocal = localize('DataScience.jupyterServerKernelLeaveRunningLocal', 'Leave Kernel running when closed'); export const jupyterSelectPasswordPrompt = localize('DataScience.jupyterSelectPasswordPrompt', 'Enter your notebook password'); export const jupyterNotebookFailure = localize('DataScience.jupyterNotebookFailure', 'Jupyter notebook failed to launch. \r\n{0}'); export const jupyterNotebookConnectFailed = localize('DataScience.jupyterNotebookConnectFailed', 'Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}'); diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index dc630fb1957c..1b941777a15c 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -309,7 +309,7 @@ export class DataScience implements IDataScience { }; }); const startNewKernel = { - label: 'Start new kernel on Jupyter server', + label: localize.DataScience.jupyterServerReconnectKernelStartNewLocal(), picked: true, kernelId: 'none' }; @@ -317,15 +317,15 @@ export class DataScience implements IDataScience { const kernelSelection = await this.appShell.showQuickPick(arr, { ignoreFocusOut: true, - placeHolder: 'Select Jupyer Kernel' + placeHolder: localize.DataScience.jupyterServerReconnectKernelLocal() }); const autoShutdown = { - label: 'Automatically shutdown Kernel when closed', + label: localize.DataScience.jupyterServerKernelAutoShutdownLocal(), picked: true }; const leaveRunning = { - label: 'Leave Kernel running when closed', + label: localize.DataScience.jupyterServerKernelLeaveRunningLocal(), picked: true }; const shutdownOptions = [autoShutdown, leaveRunning]; From 8059106c2bdc08512f7b450db3f75a163bb0e347 Mon Sep 17 00:00:00 2001 From: Kyle Kavanagh Date: Sun, 18 Aug 2019 15:45:14 -0500 Subject: [PATCH 07/13] Linting --- src/client/common/types.ts | 4 +- src/client/datascience/datascience.ts | 3 +- .../datascience/jupyter/jupyterExecution.ts | 50 +++++++++---------- .../datascience/jupyter/jupyterSession.ts | 2 - .../jupyter/jupyterSessionManager.ts | 5 +- src/test/datascience/mockJupyterManager.ts | 2 +- 6 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 0e223ca96efc..fa251867c7d0 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -303,8 +303,8 @@ export interface IDataScienceSettings { jupyterInterruptTimeout: number; jupyterLaunchTimeout: number; jupyterLaunchRetries: number; - jupyterServerAllowKernelShutdown: boolean, - jupyterServerKernelId: string, + jupyterServerAllowKernelShutdown: boolean; + jupyterServerKernelId: string; jupyterServerURI: string; notebookFileRoot: string; changeDirOnImportExport: boolean; diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index 1b941777a15c..feb42371b513 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. 'use strict'; import '../common/extensions'; + import { Kernel } from '@jupyterlab/services'; import { JSONObject } from '@phosphor/coreutils'; import { inject, injectable } from 'inversify'; @@ -11,7 +12,7 @@ import * as vscode from 'vscode'; import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, IWorkspaceService } from '../common/application/types'; import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants'; import { ContextKey } from '../common/contextKey'; -import { traceInfo, traceError } from '../common/logger'; +import { traceError, traceInfo } from '../common/logger'; import { BANNER_NAME_DS_SURVEY, IConfigurationService, diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 6d4e308f1061..a75de37758d0 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -49,6 +49,10 @@ enum ModuleExistsResult { export class JupyterExecutionBase implements IJupyterExecution { + public get sessionChanged(): Event { + return this.eventEmitter.event; + } + private processServicePromise: Promise; private commands: Record = {}; private jupyterPath: string | undefined; @@ -86,8 +90,27 @@ export class JupyterExecutionBase implements IJupyterExecution { } } - public get sessionChanged(): Event { - return this.eventEmitter.event; + public static createRemoteConnectionInfo = (uri: string, configuration: IConfigurationService): IConnection => { + let url: URL; + try { + url = new URL(uri); + } catch (err) { + // This should already have been parsed when set, so just throw if it's not right here + throw err; + } + const settings = configuration.getSettings(); + const allowUnauthorized = settings.datascience.allowUnauthorizedRemoteConnection ? settings.datascience.allowUnauthorizedRemoteConnection : false; + + return { + allowUnauthorized, + baseUrl: `${url.protocol}//${url.host}${url.pathname}`, + token: `${url.searchParams.get('token')}`, + hostName: url.hostname, + localLaunch: false, + localProcExitCode: undefined, + disconnected: (_l) => { return { dispose: noop }; }, + dispose: noop + }; } public dispose(): Promise { @@ -310,29 +333,6 @@ export class JupyterExecutionBase implements IJupyterExecution { return { connection, kernelSpec }; } - public static createRemoteConnectionInfo = (uri: string, configuration: IConfigurationService): IConnection => { - let url: URL; - try { - url = new URL(uri); - } catch (err) { - // This should already have been parsed when set, so just throw if it's not right here - throw err; - } - const settings = configuration.getSettings(); - const allowUnauthorized = settings.datascience.allowUnauthorizedRemoteConnection ? settings.datascience.allowUnauthorizedRemoteConnection : false; - - return { - allowUnauthorized, - baseUrl: `${url.protocol}//${url.host}${url.pathname}`, - token: `${url.searchParams.get('token')}`, - hostName: url.hostname, - localLaunch: false, - localProcExitCode: undefined, - disconnected: (_l) => { return { dispose: noop }; }, - dispose: noop - }; - } - // tslint:disable-next-line: max-func-body-length @captureTelemetry(Telemetry.StartJupyter) private async startNotebookServer(useDefaultConfig: boolean, cancelToken?: CancellationToken): Promise<{ connection: IConnection; kernelSpec: IJupyterKernelSpec | undefined }> { diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index bfaf2993bbc0..d1f65590aa4a 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -160,7 +160,6 @@ export class JupyterSession implements IJupyterSession { this.sessionManager = new SessionManager({ serverSettings: serverSettings }); this.contentsManager = new ContentsManager({ serverSettings: serverSettings }); - // Start a new session this.session = await this.createSession(serverSettings, this.contentsManager, cancelToken); @@ -224,7 +223,6 @@ export class JupyterSession implements IJupyterSession { throw exception; } - private async createSession(serverSettings: ServerConnection.ISettings, contentsManager: ContentsManager, cancelToken?: CancellationToken): Promise { // Create a temporary notebook for this session. Give it a unique name, so there is no possiblity of us reusing a session lingering the jupyter server diff --git a/src/client/datascience/jupyter/jupyterSessionManager.ts b/src/client/datascience/jupyter/jupyterSessionManager.ts index 719860d238d7..089a2b19935b 100644 --- a/src/client/datascience/jupyter/jupyterSessionManager.ts +++ b/src/client/datascience/jupyter/jupyterSessionManager.ts @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { ServerConnection, SessionManager, Kernel } from '@jupyterlab/services'; +import { Kernel, ServerConnection, SessionManager } from '@jupyterlab/services'; import { inject, injectable } from 'inversify'; import { CancellationToken } from 'vscode-jsonrpc'; -import { IConnection, IJupyterKernelSpec, IJupyterPasswordConnect, IJupyterSession, IJupyterSessionManager } from '../types'; import { IConfigurationService } from '../../common/types'; +import { IConnection, IJupyterKernelSpec, IJupyterPasswordConnect, IJupyterSession, IJupyterSessionManager } from '../types'; import { JupyterKernelSpec } from './jupyterKernelSpec'; import { JupyterSession } from './jupyterSession'; @@ -66,7 +66,6 @@ export class JupyterSessionManager implements IJupyterSessionManager { } - private makeServerSettings(connection: IConnection): ServerConnection.ISettings { return ServerConnection.makeSettings( { diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index c43a832dc575..bf45dc4cd8ee 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -12,6 +12,7 @@ import * as uuid from 'uuid/v4'; import { EventEmitter } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; +import { Kernel } from '@jupyterlab/services'; import { Cancellation } from '../../client/common/cancellation'; import { ExecutionResult, IProcessServiceFactory, IPythonExecutionFactory, Output } from '../../client/common/process/types'; import { IAsyncDisposableRegistry, IConfigurationService } from '../../client/common/types'; @@ -33,7 +34,6 @@ import { noop, sleep } from '../core'; import { MockJupyterSession } from './mockJupyterSession'; import { MockProcessService } from './mockProcessService'; import { MockPythonService } from './mockPythonService'; -import { Kernel } from '@jupyterlab/services'; // tslint:disable:no-any no-http-string no-multiline-string max-func-body-length From df99bbed36a38749fbec9c0ef67c023d5e372664 Mon Sep 17 00:00:00 2001 From: hochshi Date: Mon, 2 Sep 2019 13:55:58 +0300 Subject: [PATCH 08/13] Fixed some requested changes and modified the service to use the full IJupyterKernelSpec instead of storing just the uuid in the settings. --- package.json | 4 +-- package.nls.json | 8 ++--- src/client/common/types.ts | 2 ++ src/client/datascience/datascience.ts | 34 ++++++++++++++----- .../interactiveWindowProvider.ts | 24 ++++++------- .../datascience/jupyter/jupyterExecution.ts | 7 ++-- .../datascience/jupyter/jupyterKernelSpec.ts | 3 +- .../datascience/jupyter/jupyterSession.ts | 24 ++++++------- .../jupyter/jupyterSessionManager.ts | 15 ++++---- src/client/datascience/types.ts | 9 ++++- .../react-common/settingsReactSide.ts | 1 + src/test/datascience/color.test.ts | 1 + .../datascience/dataScienceIocContainer.ts | 1 + .../codewatcher.unit.test.ts | 1 + src/test/datascience/execution.unit.test.ts | 1 + ...eractiveWindowCommandListener.unit.test.ts | 1 + .../interactiveWindowTestHelpers.tsx | 1 + 17 files changed, 88 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 526f8a0ac77a..e13f0b17fa3c 100644 --- a/package.json +++ b/package.json @@ -1370,13 +1370,13 @@ "python.dataScience.jupyterServerKernelId": { "type": "string", "default": "", - "description": "Select the Jupyter server Kernel UUID to connect to. Leave blank to start a new kernel", + "description": "Select the Jupyter server kernel UUID to connect to. Leave blank to start a new kernel", "scope": "resource" }, "python.dataScience.jupyterServerAllowKernelShutdown": { "type": "boolean", "default": true, - "description": "Shutdown the Jupyter kernel when finished", + "description": "Shutdown the Jupyter kernel when finished.", "scope": "resource" }, "python.dataScience.notebookFileRoot": { diff --git a/package.nls.json b/package.nls.json index a92fd9f2b97a..03fc435f4501 100644 --- a/package.nls.json +++ b/package.nls.json @@ -174,10 +174,10 @@ "DataScience.jupyterSelectURISpecifyURI": "Type in the URI to connect to a running Jupyter server", "DataScience.jupyterSelectURIPrompt": "Enter the URI of a Jupyter server", "DataScience.jupyterSelectURIInvalidURI": "Invalid URI specified", - "DataScience.jupyterServerReconnectKernelLocal": "Select Jupyer Kernel", - "DataScience.jupyterServerReconnectKernelStartNewLocal": "Start new kernel on Jupyter server", - "DataScience.jupyterServerKernelAutoShutdownLocal": "Automatically shutdown Kernel when closed", - "DataScience.jupyterServerKernelLeaveRunningLocal": "Leave Kernel running when closed", + "DataScience.jupyterServerReconnectKernel": "Select Jupyer kernel", + "DataScience.jupyterServerReconnectKernelStartNew": "Start new kernel on Jupyter server", + "DataScience.jupyterServerKernelAutoShutdown": "Automatically shutdown kernel when closed", + "DataScience.jupyterServerKernelLeaveRunning": "Leave kernel running when closed", "DataScience.jupyterSelectPasswordPrompt": "Enter your notebook password", "DataScience.jupyterNotebookFailure": "Jupyter notebook failed to launch. \r\n{0}", "DataScience.jupyterNotebookConnectFailed": "Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}", diff --git a/src/client/common/types.ts b/src/client/common/types.ts index fa251867c7d0..25ff612f2f23 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -5,6 +5,7 @@ import { Socket } from 'net'; import { Request as RequestResult } from 'request'; import { ConfigurationTarget, DiagnosticSeverity, Disposable, DocumentSymbolProvider, Event, Extension, ExtensionContext, OutputChannel, Uri, WorkspaceEdit } from 'vscode'; +import { IJupyterKernelSpec } from '../datascience/types'; import { CommandsWithoutArgs } from './application/commands'; import { ExtensionChannels } from './insidersBuild/types'; import { EnvironmentVariables } from './variables/types'; @@ -305,6 +306,7 @@ export interface IDataScienceSettings { jupyterLaunchRetries: number; jupyterServerAllowKernelShutdown: boolean; jupyterServerKernelId: string; + jupyterServerKernelSpec: IJupyterKernelSpec | undefined; jupyterServerURI: string; notebookFileRoot: string; changeDirOnImportExport: boolean; diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index feb42371b513..b446d2b366a1 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -28,11 +28,15 @@ import { captureTelemetry, sendTelemetryEvent } from '../telemetry'; import { hasCells } from './cellFactory'; import { Commands, EditorContexts, Settings, Telemetry } from './constants'; import { JupyterExecutionBase } from './jupyter/jupyterExecution'; -import { ICodeWatcher, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener, IJupyterSessionManager } from './types'; - -interface IKernelQuickPickItem extends vscode.QuickPickItem { - kernelId: string; -} +import { + ICodeWatcher, + IDataScience, + IDataScienceCodeLensProvider, + IDataScienceCommandListener, + IJupyterKernelSpec, + IJupyterSessionManager, + IKernelQuickPickItem +} from './types'; @injectable() export class DataScience implements IDataScience { @@ -306,13 +310,15 @@ export class DataScience implements IDataScience { return { label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, - kernelId: runningKernel.id + kernelId: runningKernel.id, + name: runningKernel.name }; }); const startNewKernel = { label: localize.DataScience.jupyterServerReconnectKernelStartNewLocal(), picked: true, - kernelId: 'none' + kernelId: 'none', + name: 'none' }; arr.unshift(startNewKernel); @@ -332,10 +338,12 @@ export class DataScience implements IDataScience { const shutdownOptions = [autoShutdown, leaveRunning]; const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); - let kernelUUID: string = ''; + let kernelUUID: string | undefined = ''; + let kernelName: string | undefined = ''; if (kernelSelection && kernelSelection !== startNewKernel) { traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); kernelUUID = kernelSelection.kernelId; + kernelName = kernelSelection.name; sendTelemetryEvent(Telemetry.JupyterKernelSpecified); } else { @@ -343,6 +351,16 @@ export class DataScience implements IDataScience { } await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelUUID, undefined, vscode.ConfigurationTarget.Workspace); + let kernelSpec: IJupyterKernelSpec | undefined; + if (kernelName) { + let kernelSpecs: IJupyterKernelSpec[] = await this.sessionManager.getActiveKernelSpecs(connInfo); + kernelSpecs = kernelSpecs.filter(spec => spec.name === kernelName); + if (kernelSpecs.length > 0) { + kernelSpec = kernelSpecs[0]; + } + } + await this.configuration.updateSetting('dataScience.jupyterServerKernelSpec', kernelSpec, undefined, vscode.ConfigurationTarget.Workspace); + let allowShutdown = true; if (shutdownSelection === leaveRunning) { traceInfo('Session will not be shutdown on close'); diff --git a/src/client/datascience/interactive-window/interactiveWindowProvider.ts b/src/client/datascience/interactive-window/interactiveWindowProvider.ts index f546bbd799d8..482924265e01 100644 --- a/src/client/datascience/interactive-window/interactiveWindowProvider.ts +++ b/src/client/datascience/interactive-window/interactiveWindowProvider.ts @@ -23,19 +23,19 @@ interface ISyncData { @injectable() export class InteractiveWindowProvider implements IInteractiveWindowProvider, IAsyncDisposable { - private activeInteractiveWindow : IInteractiveWindow | undefined; - private postOffice : PostOffice; + private activeInteractiveWindow: IInteractiveWindow | undefined; + private postOffice: PostOffice; private id: string; - private pendingSyncs : Map = new Map(); + private pendingSyncs: Map = new Map(); private executedCode: EventEmitter = new EventEmitter(); private activeInteractiveWindowExecuteHandler: Disposable | undefined; constructor( @inject(ILiveShareApi) liveShare: ILiveShareApi, @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IAsyncDisposableRegistry) asyncRegistry : IAsyncDisposableRegistry, + @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, @inject(IDisposableRegistry) private disposables: IDisposableRegistry, @inject(IConfigurationService) private configService: IConfigurationService - ) { + ) { asyncRegistry.push(this); // Create a post office so we can make sure interactive windows are created at the same time @@ -53,15 +53,15 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA this.id = uuid(); } - public getActive() : IInteractiveWindow | undefined { + public getActive(): IInteractiveWindow | undefined { return this.activeInteractiveWindow; } - public get onExecutedCode() : Event { + public get onExecutedCode(): Event { return this.executedCode.event; } - public async getOrCreateActive() : Promise { + public async getOrCreateActive(): Promise { if (!this.activeInteractiveWindow) { await this.create(); } @@ -77,7 +77,7 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA throw new Error(localize.DataScience.pythonInteractiveCreateFailed()); } - public async getNotebookOptions() : Promise { + public async getNotebookOptions(): Promise { // Find the settings that we are going to launch our server with const settings = this.configService.getSettings(); let serverURI: string | undefined = settings.datascience.jupyterServerURI; @@ -96,11 +96,11 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA }; } - public dispose() : Promise { + public dispose(): Promise { return this.postOffice.dispose(); } - private async create() : Promise { + private async create(): Promise { // Set it as soon as we create it. The .ctor for the interactive window // may cause a subclass to talk to the IInteractiveWindowProvider to get the active interactive window. this.activeInteractiveWindow = this.serviceContainer.get(IInteractiveWindow); @@ -162,7 +162,7 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA } } - private async synchronizeCreate() : Promise { + private async synchronizeCreate(): Promise { // Create a new pending wait if necessary if (this.postOffice.peerCount > 0 || this.postOffice.role === vsls.Role.Guest) { const key = uuid(); diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index a75de37758d0..d39a001d6286 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -100,6 +100,7 @@ export class JupyterExecutionBase implements IJupyterExecution { } const settings = configuration.getSettings(); const allowUnauthorized = settings.datascience.allowUnauthorizedRemoteConnection ? settings.datascience.allowUnauthorizedRemoteConnection : false; + const allowShutdown: boolean | undefined = settings.datascience.jupyterServerAllowKernelShutdown; return { allowUnauthorized, @@ -109,7 +110,8 @@ export class JupyterExecutionBase implements IJupyterExecution { localLaunch: false, localProcExitCode: undefined, disconnected: (_l) => { return { dispose: noop }; }, - dispose: noop + dispose: noop, + allowShutdown: allowShutdown }; } @@ -315,7 +317,8 @@ export class JupyterExecutionBase implements IJupyterExecution { } else { // If we have a URI spec up a connection info for it connection = JupyterExecutionBase.createRemoteConnectionInfo(options.uri, this.configuration); - kernelSpec = undefined; + const settings = this.configuration.getSettings(); + kernelSpec = settings.datascience.jupyterServerKernelSpec; } // If we don't have a kernel spec yet, check using our current connection diff --git a/src/client/datascience/jupyter/jupyterKernelSpec.ts b/src/client/datascience/jupyter/jupyterKernelSpec.ts index 9881db8671c7..57eb5b87702d 100644 --- a/src/client/datascience/jupyter/jupyterKernelSpec.ts +++ b/src/client/datascience/jupyter/jupyterKernelSpec.ts @@ -15,7 +15,8 @@ export class JupyterKernelSpec implements IJupyterKernelSpec { public language: string; public path: string; public specFile: string | undefined; - constructor(specModel : Kernel.ISpecModel, file?: string) { + public id: string | undefined; + constructor(specModel: Kernel.ISpecModel, file?: string) { this.name = specModel.name; this.language = specModel.language; this.path = specModel.argv && specModel.argv.length > 0 ? specModel.argv[0] : ''; diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index d1f65590aa4a..9d3bb5f02273 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -47,21 +47,21 @@ export class JupyterSession implements IJupyterSession { private connected: boolean = false; private jupyterPasswordConnect: IJupyterPasswordConnect; private oldSessions: Session.ISession[] = []; - private allowShutdown: boolean; - private desiredKernelId: string | undefined; + // private allowShutdown: boolean; + // private desiredKernelId: string | undefined; constructor( connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, - jupyterPasswordConnect: IJupyterPasswordConnect, - desiredKernelId: string | undefined, - allowShutdown: boolean + jupyterPasswordConnect: IJupyterPasswordConnect + // desiredKernelId: string | undefined, + // allowShutdown: boolean ) { this.connInfo = connInfo; this.kernelSpec = kernelSpec; this.jupyterPasswordConnect = jupyterPasswordConnect; - this.allowShutdown = allowShutdown; - this.desiredKernelId = desiredKernelId; + // this.allowShutdown = allowShutdown; + // this.desiredKernelId = desiredKernelId; } public dispose(): Promise { @@ -239,9 +239,9 @@ export class JupyterSession implements IJupyterSession { serverSettings: serverSettings }; - if (this.desiredKernelId) { - options.kernelId = this.desiredKernelId; - traceInfo(`Connecting to existing kernel ${this.desiredKernelId}`); + if (this.kernelSpec && this.kernelSpec.id) { + options.kernelId = this.kernelSpec.id; + traceInfo(`Connecting to existing kernel ${this.kernelSpec.id}`); } else { traceInfo('Creating new kernel for connection'); } @@ -365,11 +365,11 @@ export class JupyterSession implements IJupyterSession { }); } } - if (this.allowShutdown) { + if (this.connInfo && this.connInfo.allowShutdown) { await waitForPromise(session.shutdown(), 1000); } } else { - if (this.allowShutdown) { + if (this.connInfo && this.connInfo.allowShutdown) { // Shutdown may fail if the process has been killed await waitForPromise(session.shutdown(), 1000); } diff --git a/src/client/datascience/jupyter/jupyterSessionManager.ts b/src/client/datascience/jupyter/jupyterSessionManager.ts index 089a2b19935b..1210d4b5ee71 100644 --- a/src/client/datascience/jupyter/jupyterSessionManager.ts +++ b/src/client/datascience/jupyter/jupyterSessionManager.ts @@ -5,7 +5,7 @@ import { Kernel, ServerConnection, SessionManager } from '@jupyterlab/services'; import { inject, injectable } from 'inversify'; import { CancellationToken } from 'vscode-jsonrpc'; -import { IConfigurationService } from '../../common/types'; +// import { IConfigurationService } from '../../common/types'; import { IConnection, IJupyterKernelSpec, IJupyterPasswordConnect, IJupyterSession, IJupyterSessionManager } from '../types'; import { JupyterKernelSpec } from './jupyterKernelSpec'; import { JupyterSession } from './jupyterSession'; @@ -13,16 +13,17 @@ import { JupyterSession } from './jupyterSession'; @injectable() export class JupyterSessionManager implements IJupyterSessionManager { constructor( - @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService + @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect + // @inject(IConfigurationService) private readonly configurationService: IConfigurationService ) { } public async startNew(connInfo: IConnection, kernelSpec: IJupyterKernelSpec | undefined, cancelToken?: CancellationToken): Promise { // Create a new session and attempt to connect to it - const settings = this.configurationService.getSettings(); - const allowShutdown = settings.datascience.jupyterServerAllowKernelShutdown; - const kernelId = settings.datascience.jupyterServerKernelId; - const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect, kernelId, allowShutdown); + // const settings = this.configurationService.getSettings(); + // const allowShutdown = settings.datascience.jupyterServerAllowKernelShutdown; + // const kernelId = settings.datascience.jupyterServerKernelId; + // const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect, kernelId, allowShutdown); + const session = new JupyterSession(connInfo, kernelSpec, this.jupyterPasswordConnect); try { await session.connect(cancelToken); } finally { diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 93207aa513b6..9e9b42f80f5c 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -5,7 +5,7 @@ import { nbformat } from '@jupyterlab/coreutils'; import { Kernel, KernelMessage } from '@jupyterlab/services/lib/kernel'; import { JSONObject } from '@phosphor/coreutils'; import { Observable } from 'rxjs/Observable'; -import { CancellationToken, CodeLens, CodeLensProvider, DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, Disposable, Event, Range, TextDocument, TextEditor } from 'vscode'; +import { CancellationToken, CodeLens, CodeLensProvider, DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, Disposable, Event, QuickPickItem, Range, TextDocument, TextEditor } from 'vscode'; import { ICommandManager } from '../common/application/types'; import { ExecutionResult, ObservableExecutionResult, SpawnOptions } from '../common/process/types'; @@ -34,6 +34,7 @@ export interface IConnection extends Disposable { localProcExitCode: number | undefined; disconnected: Event; allowUnauthorized?: boolean; + allowShutdown?: boolean; } export enum InterruptResult { @@ -155,6 +156,12 @@ export interface IJupyterKernelSpec extends IAsyncDisposable { name: string | undefined; language: string | undefined; path: string | undefined; + id: string | undefined; +} + +export interface IKernelQuickPickItem extends QuickPickItem { + name: string | undefined; + kernelId: string | undefined; } export const INotebookImporter = Symbol('INotebookImporter'); diff --git a/src/datascience-ui/react-common/settingsReactSide.ts b/src/datascience-ui/react-common/settingsReactSide.ts index 35f269cd73fa..6ca9d98a99ba 100644 --- a/src/datascience-ui/react-common/settingsReactSide.ts +++ b/src/datascience-ui/react-common/settingsReactSide.ts @@ -38,6 +38,7 @@ function load() { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/color.test.ts b/src/test/datascience/color.test.ts index 6638a62d5616..390b36a3e34f 100644 --- a/src/test/datascience/color.test.ts +++ b/src/test/datascience/color.test.ts @@ -55,6 +55,7 @@ suite('Theme colors', () => { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 02da59ba6710..79817af8c0d1 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -409,6 +409,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index c9efadb5f096..723276cd9098 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -76,6 +76,7 @@ suite('DataScience Code Watcher Unit Tests', () => { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index b8e3ad409208..287be7d938e2 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -542,6 +542,7 @@ suite('Jupyter Execution', async () => { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts index 9caa0a36faae..44f750ce5ab8 100644 --- a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts +++ b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts @@ -135,6 +135,7 @@ suite('Interactive window command listener', async () => { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: '', changeDirOnImportExport: true, notebookFileRoot: 'WORKSPACE', diff --git a/src/test/datascience/interactiveWindowTestHelpers.tsx b/src/test/datascience/interactiveWindowTestHelpers.tsx index b95fa55c5e17..e2c50c6fe8b2 100644 --- a/src/test/datascience/interactiveWindowTestHelpers.tsx +++ b/src/test/datascience/interactiveWindowTestHelpers.tsx @@ -309,6 +309,7 @@ export function defaultDataScienceSettings(): IDataScienceSettings { enabled: true, jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', + jupyterServerKernelSpec: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, From b4d487d873a7015c62fff34a783b04dc01e15fde Mon Sep 17 00:00:00 2001 From: hochshi Date: Mon, 2 Sep 2019 17:29:36 +0300 Subject: [PATCH 09/13] Rewrote a few lines to make the code a little more managable. --- package.json | 6 ++ src/client/datascience/datascience.ts | 150 ++++++++++++++++---------- src/client/datascience/types.ts | 6 ++ 3 files changed, 105 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index e13f0b17fa3c..a46e853660a4 100644 --- a/package.json +++ b/package.json @@ -1368,6 +1368,12 @@ "scope": "resource" }, "python.dataScience.jupyterServerKernelId": { + "type": "object", + "default": "", + "description": "Select the Jupyter server kernel UUID to connect to. Leave blank to start a new kernel", + "scope": "resource" + }, + "python.dataScience.jupyterServerKernelSpec": { "type": "string", "default": "", "description": "Select the Jupyter server kernel UUID to connect to. Leave blank to start a new kernel", diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index b446d2b366a1..b33c0b9a9cea 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -30,13 +30,24 @@ import { Commands, EditorContexts, Settings, Telemetry } from './constants'; import { JupyterExecutionBase } from './jupyter/jupyterExecution'; import { ICodeWatcher, + IConnection, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener, IJupyterKernelSpec, IJupyterSessionManager, + IJupyterShutdown, IKernelQuickPickItem } from './types'; +// import { Icons } from '../testing/common/constants'; +// import { Dictionary } from 'lodash'; + +const newKernel = { + label: localize.DataScience.jupyterServerReconnectKernelStartNewLocal(), + picked: true, + kernelId: '', + name: '' +}; @injectable() export class DataScience implements IDataScience { @@ -290,6 +301,61 @@ export class DataScience implements IDataScience { await this.configuration.updateSetting('dataScience.jupyterServerURI', Settings.JupyterServerLocalLaunch, undefined, vscode.ConfigurationTarget.Workspace); } + private async getJupyterKernels(connInfo: IConnection): Promise { + const runningKernels: Kernel.IModel[] = await this.sessionManager.getActiveKernels(connInfo); + const arr: IKernelQuickPickItem[] = runningKernels.map(runningKernel => { + traceInfo(`Found running kernel ${runningKernel.id}, running since ${runningKernel.last_activity}`); + const localLastActivity = runningKernel.last_activity ? new Date(runningKernel.last_activity.toString()).toLocaleString() : '?'; + return { + label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, + detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, + kernelId: runningKernel.id, + name: runningKernel.name + }; + }); + arr.unshift(newKernel); + return arr; + } + + private shutdownOptions(): IJupyterShutdown[] { + const autoShutdown: IJupyterShutdown = { + label: localize.DataScience.jupyterServerKernelAutoShutdownLocal(), + keepRunning: false, + picked: true + }; + const leaveRunning: IJupyterShutdown = { + label: localize.DataScience.jupyterServerKernelLeaveRunningLocal(), + keepRunning: true, + picked: true + }; + return [autoShutdown, leaveRunning]; + } + + private async getKernelSelection(connInfo: IConnection): Promise { + const kernelOptions: IKernelQuickPickItem[] = await this.getJupyterKernels(connInfo); + + return this.appShell.showQuickPick(kernelOptions, { + ignoreFocusOut: true, + placeHolder: localize.DataScience.jupyterServerReconnectKernelLocal() + }); + } + + private async getNewKernelSelection(kernelSpecs: IJupyterKernelSpec[]): Promise { + const availArr: IKernelQuickPickItem[] = kernelSpecs.map(availableKernel => { + traceInfo(`Found available kernel ${availableKernel.name}`); + return { + label: `Kernel ${availableKernel.name}`, + detail: '', + kernelId: '', + name: availableKernel.name + }; + }); + + return this.appShell.showQuickPick(availArr, { + ignoreFocusOut: true + }); + } + @captureTelemetry(Telemetry.SetJupyterURIToUserSpecified) private async selectJupyterLaunchURI(): Promise { // First get the proposed URI from the user @@ -298,78 +364,48 @@ export class DataScience implements IDataScience { placeHolder: 'https://hostname:8080/?token=849d61a414abafab97bc4aab1f3547755ddc232c2b8cb7fe', validateInput: this.validateURI, ignoreFocusOut: true }); + let kernelUUID: string | undefined = ''; + let kernelName: string | undefined = ''; + let kernelSpec: IJupyterKernelSpec | undefined; + let allowShutdown = true; + if (userURI) { await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); - const connInfo = JupyterExecutionBase.createRemoteConnectionInfo(userURI, this.configuration); - - const runningKernels: Kernel.IModel[] = await this.sessionManager.getActiveKernels(connInfo); - const arr: IKernelQuickPickItem[] = runningKernels.map(runningKernel => { - traceInfo(`Found running kernel ${runningKernel.id}, running since ${runningKernel.last_activity}`); - const localLastActivity = runningKernel.last_activity ? new Date(runningKernel.last_activity.toString()).toLocaleString() : '?'; - return { - label: `Kernel ${runningKernel.name} - ${runningKernel.id}`, - detail: `Running since ${localLastActivity}, ${runningKernel.connections} existing connections`, - kernelId: runningKernel.id, - name: runningKernel.name - }; - }); - const startNewKernel = { - label: localize.DataScience.jupyterServerReconnectKernelStartNewLocal(), - picked: true, - kernelId: 'none', - name: 'none' - }; - arr.unshift(startNewKernel); + const connInfo: IConnection = JupyterExecutionBase.createRemoteConnectionInfo(userURI, this.configuration); - const kernelSelection = await this.appShell.showQuickPick(arr, { - ignoreFocusOut: true, - placeHolder: localize.DataScience.jupyterServerReconnectKernelLocal() - }); + let kernelSelection: IKernelQuickPickItem | undefined = await this.getKernelSelection(connInfo); - const autoShutdown = { - label: localize.DataScience.jupyterServerKernelAutoShutdownLocal(), - picked: true - }; - const leaveRunning = { - label: localize.DataScience.jupyterServerKernelLeaveRunningLocal(), - picked: true - }; - const shutdownOptions = [autoShutdown, leaveRunning]; - const shutdownSelection = await this.appShell.showQuickPick(shutdownOptions, { ignoreFocusOut: true }); - - let kernelUUID: string | undefined = ''; - let kernelName: string | undefined = ''; - if (kernelSelection && kernelSelection !== startNewKernel) { - traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); - kernelUUID = kernelSelection.kernelId; - kernelName = kernelSelection.name; - sendTelemetryEvent(Telemetry.JupyterKernelSpecified); - - } else { + const shutdownSelection = await this.appShell.showQuickPick(this.shutdownOptions(), { ignoreFocusOut: true }); + + let kernelSpecs: IJupyterKernelSpec[] = await this.sessionManager.getActiveKernelSpecs(connInfo); + + if (kernelSelection && kernelSelection === newKernel) { traceInfo('Will create a new kernel for connection'); + kernelSelection = await this.getNewKernelSelection(kernelSpecs); } - await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelUUID, undefined, vscode.ConfigurationTarget.Workspace); + sendTelemetryEvent(Telemetry.JupyterKernelAutoShutdown, undefined, { autoShutdownEnabled: allowShutdown }); - let kernelSpec: IJupyterKernelSpec | undefined; - if (kernelName) { - let kernelSpecs: IJupyterKernelSpec[] = await this.sessionManager.getActiveKernelSpecs(connInfo); - kernelSpecs = kernelSpecs.filter(spec => spec.name === kernelName); - if (kernelSpecs.length > 0) { - kernelSpec = kernelSpecs[0]; + if (kernelSelection) { + if (kernelSelection !== newKernel) { + traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); + sendTelemetryEvent(Telemetry.JupyterKernelSpecified); } + kernelUUID = kernelSelection.kernelId ? kernelSelection.kernelId : undefined; + kernelName = kernelSelection.name ? kernelSelection.name : undefined; + kernelSpecs = kernelSpecs.filter(spec => spec.name === kernelName); + kernelSpec = kernelSpecs.length === 1 ? kernelSpecs[0] : undefined; } - await this.configuration.updateSetting('dataScience.jupyterServerKernelSpec', kernelSpec, undefined, vscode.ConfigurationTarget.Workspace); - let allowShutdown = true; - if (shutdownSelection === leaveRunning) { + if (shutdownSelection && shutdownSelection.keepRunning) { traceInfo('Session will not be shutdown on close'); allowShutdown = false; } - sendTelemetryEvent(Telemetry.JupyterKernelAutoShutdown, undefined, { autoShutdownEnabled: allowShutdown }); - await this.configuration.updateSetting('dataScience.jupyterServerAllowKernelShutdown', allowShutdown, undefined, vscode.ConfigurationTarget.Workspace); - } + await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); + await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelUUID, undefined, vscode.ConfigurationTarget.Workspace); + await this.configuration.updateSetting('dataScience.jupyterServerKernelSpec', kernelSpec, undefined, vscode.ConfigurationTarget.Workspace); + await this.configuration.updateSetting('dataScience.jupyterServerAllowKernelShutdown', allowShutdown, undefined, vscode.ConfigurationTarget.Workspace); } @captureTelemetry(Telemetry.AddCellBelow) diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 9e9b42f80f5c..94aa05ff2fbc 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -164,6 +164,12 @@ export interface IKernelQuickPickItem extends QuickPickItem { kernelId: string | undefined; } +export interface IJupyterShutdown { + label: string; + picked: boolean; + keepRunning: boolean; +} + export const INotebookImporter = Symbol('INotebookImporter'); export interface INotebookImporter extends Disposable { importFromFile(file: string): Promise; From 49a2ccef03a2dfe1824bc048406ed7403f2fadca Mon Sep 17 00:00:00 2001 From: hochshi Date: Tue, 3 Sep 2019 07:54:41 +0300 Subject: [PATCH 10/13] Added a jupyter servers settings so the user won't have to enter the URI each time. This seems to have messed up the ability to connect to the same server and start a new kernel. --- package.json | 12 +- src/client/common/types.ts | 3 +- src/client/datascience/datascience.ts | 134 +++++++++++++----- src/client/datascience/types.ts | 10 ++ .../react-common/settingsReactSide.ts | 1 + src/test/datascience/color.test.ts | 1 + .../datascience/dataScienceIocContainer.ts | 1 + .../codewatcher.unit.test.ts | 1 + src/test/datascience/execution.unit.test.ts | 1 + ...eractiveWindowCommandListener.unit.test.ts | 1 + .../interactiveWindowTestHelpers.tsx | 1 + 11 files changed, 123 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index a46e853660a4..acfa045262fa 100644 --- a/package.json +++ b/package.json @@ -1361,6 +1361,12 @@ "description": "Number of times to attempt to connect to the Jupyter Notebook", "scope": "resource" }, + "python.dataScience.jupyterServers": { + "type": "array", + "default": [], + "description": "Jupter servers.", + "scope": "resource" + }, "python.dataScience.jupyterServerURI": { "type": "string", "default": "local", @@ -1368,15 +1374,15 @@ "scope": "resource" }, "python.dataScience.jupyterServerKernelId": { - "type": "object", + "type": "string", "default": "", "description": "Select the Jupyter server kernel UUID to connect to. Leave blank to start a new kernel", "scope": "resource" }, "python.dataScience.jupyterServerKernelSpec": { - "type": "string", + "type": "object", "default": "", - "description": "Select the Jupyter server kernel UUID to connect to. Leave blank to start a new kernel", + "description": "Specify kernel spec to connect to. Leave blank to start a new kernel", "scope": "resource" }, "python.dataScience.jupyterServerAllowKernelShutdown": { diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 25ff612f2f23..fa66b1345eb7 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -5,7 +5,7 @@ import { Socket } from 'net'; import { Request as RequestResult } from 'request'; import { ConfigurationTarget, DiagnosticSeverity, Disposable, DocumentSymbolProvider, Event, Extension, ExtensionContext, OutputChannel, Uri, WorkspaceEdit } from 'vscode'; -import { IJupyterKernelSpec } from '../datascience/types'; +import { IJupyterKernelSpec, IJupyterServer } from '../datascience/types'; import { CommandsWithoutArgs } from './application/commands'; import { ExtensionChannels } from './insidersBuild/types'; import { EnvironmentVariables } from './variables/types'; @@ -307,6 +307,7 @@ export interface IDataScienceSettings { jupyterServerAllowKernelShutdown: boolean; jupyterServerKernelId: string; jupyterServerKernelSpec: IJupyterKernelSpec | undefined; + jupyterServers: IJupyterServer[] | undefined; jupyterServerURI: string; notebookFileRoot: string; changeDirOnImportExport: boolean; diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index b33c0b9a9cea..5907b3614fdc 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -37,8 +37,12 @@ import { IJupyterKernelSpec, IJupyterSessionManager, IJupyterShutdown, - IKernelQuickPickItem + IKernelQuickPickItem, + IJupyterServer, + IJupyterServerQuickPickItem } from './types'; +// import { hostname } from 'os'; +// import { Uri } from 'monaco-editor'; // import { Icons } from '../testing/common/constants'; // import { Dictionary } from 'lodash'; @@ -49,6 +53,18 @@ const newKernel = { name: '' }; +const localJupyter: IJupyterServerQuickPickItem = { + label: localize.DataScience.jupyterSelectURILaunchLocal(), + hostName: 'local', + uri: '' +}; + +const specifyJupyter: IJupyterServerQuickPickItem = { + label: localize.DataScience.jupyterSelectURISpecifyURI(), + hostName: '', + uri: '' +}; + @injectable() export class DataScience implements IDataScience { public isDisposed: boolean = false; @@ -239,19 +255,53 @@ export class DataScience implements IDataScience { @captureTelemetry(Telemetry.SelectJupyterURI) public async selectJupyterURI(): Promise { - const quickPickOptions = [localize.DataScience.jupyterSelectURILaunchLocal(), localize.DataScience.jupyterSelectURISpecifyURI()]; - const selection = await this.appShell.showQuickPick(quickPickOptions, { ignoreFocusOut: true }); + const settings = this.configuration.getSettings(); + const jupyterServers: IJupyterServer[] | undefined = settings.datascience.jupyterServers; + let optionsArr: IJupyterServerQuickPickItem[] = []; + if (jupyterServers) { + optionsArr = jupyterServers.map(server => { + traceInfo(`Found server ${server.hostName}, with uri ${server.uri}`); + return { + label: server.hostName, + hostName: server.hostName, + uri: server.uri + }; + }); + } + optionsArr.unshift(localJupyter); + optionsArr.push(specifyJupyter); + + // const quickPickOptions = [localize.DataScience.jupyterSelectURILaunchLocal(), localize.DataScience.jupyterSelectURISpecifyURI()]; + const selection = await this.appShell.showQuickPick(optionsArr, { ignoreFocusOut: true }); + if (!selection) { + return; + } + + let connInfo: IConnection | undefined; + switch (selection) { - case localize.DataScience.jupyterSelectURILaunchLocal(): + case localJupyter: return this.setJupyterURIToLocal(); break; - case localize.DataScience.jupyterSelectURISpecifyURI(): - return this.selectJupyterLaunchURI(); + case specifyJupyter: + const userURI = await this.appShell.showInputBox({ + prompt: localize.DataScience.jupyterSelectURIPrompt(), + placeHolder: 'https://hostname:8080/?token=849d61a414abafab97bc4aab1f3547755ddc232c2b8cb7fe', validateInput: this.validateURI, ignoreFocusOut: true + }); + if (userURI) { + connInfo = await this.createConnectionInfo(userURI, true); + } break; + // return this.selectJupyterLaunchURI(); + // break; default: // If user cancels quick pick we will get undefined as the selection and fall through here + connInfo = await this.createConnectionInfo(selection.uri); break; } + if (connInfo) { + return this.selectJupyterLaunchURI(connInfo); + } } public async debugCell(file: string, startLine: number, startChar: number, endLine: number, endChar: number): Promise { @@ -356,53 +406,59 @@ export class DataScience implements IDataScience { }); } + private async createConnectionInfo(uri: string, addToSettings: boolean = false): Promise { + await this.configuration.updateSetting('dataScience.jupyterServerURI', uri, undefined, vscode.ConfigurationTarget.Workspace); + const connInfo: IConnection = JupyterExecutionBase.createRemoteConnectionInfo(uri, this.configuration); + if (addToSettings) { + const settings = this.configuration.getSettings(); + let jupyterServers: IJupyterServer[] = [{ + hostName: connInfo.hostName, + uri: uri + }]; + const savedjupyterServers = settings.datascience.jupyterServers; + if (savedjupyterServers) { + jupyterServers = jupyterServers.concat(savedjupyterServers); + } + await this.configuration.updateSetting('dataScience.jupyterServers', jupyterServers, undefined, vscode.ConfigurationTarget.Workspace); + } + return connInfo; + } + @captureTelemetry(Telemetry.SetJupyterURIToUserSpecified) - private async selectJupyterLaunchURI(): Promise { - // First get the proposed URI from the user - const userURI = await this.appShell.showInputBox({ - prompt: localize.DataScience.jupyterSelectURIPrompt(), - placeHolder: 'https://hostname:8080/?token=849d61a414abafab97bc4aab1f3547755ddc232c2b8cb7fe', validateInput: this.validateURI, ignoreFocusOut: true - }); + private async selectJupyterLaunchURI(connInfo: IConnection): Promise { let kernelUUID: string | undefined = ''; let kernelName: string | undefined = ''; let kernelSpec: IJupyterKernelSpec | undefined; let allowShutdown = true; - if (userURI) { - await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); - - const connInfo: IConnection = JupyterExecutionBase.createRemoteConnectionInfo(userURI, this.configuration); + let kernelSelection: IKernelQuickPickItem | undefined = await this.getKernelSelection(connInfo); - let kernelSelection: IKernelQuickPickItem | undefined = await this.getKernelSelection(connInfo); + const shutdownSelection = await this.appShell.showQuickPick(this.shutdownOptions(), { ignoreFocusOut: true }); - const shutdownSelection = await this.appShell.showQuickPick(this.shutdownOptions(), { ignoreFocusOut: true }); + let kernelSpecs: IJupyterKernelSpec[] = await this.sessionManager.getActiveKernelSpecs(connInfo); - let kernelSpecs: IJupyterKernelSpec[] = await this.sessionManager.getActiveKernelSpecs(connInfo); - - if (kernelSelection && kernelSelection === newKernel) { - traceInfo('Will create a new kernel for connection'); - kernelSelection = await this.getNewKernelSelection(kernelSpecs); - } - sendTelemetryEvent(Telemetry.JupyterKernelAutoShutdown, undefined, { autoShutdownEnabled: allowShutdown }); + if (kernelSelection && kernelSelection === newKernel) { + traceInfo('Will create a new kernel for connection'); + kernelSelection = await this.getNewKernelSelection(kernelSpecs); + } + sendTelemetryEvent(Telemetry.JupyterKernelAutoShutdown, undefined, { autoShutdownEnabled: allowShutdown }); - if (kernelSelection) { - if (kernelSelection !== newKernel) { - traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); - sendTelemetryEvent(Telemetry.JupyterKernelSpecified); - } - kernelUUID = kernelSelection.kernelId ? kernelSelection.kernelId : undefined; - kernelName = kernelSelection.name ? kernelSelection.name : undefined; - kernelSpecs = kernelSpecs.filter(spec => spec.name === kernelName); - kernelSpec = kernelSpecs.length === 1 ? kernelSpecs[0] : undefined; + if (kernelSelection) { + if (kernelSelection !== newKernel) { + traceInfo(`Will connect to existing kernel ${kernelSelection.kernelId}`); + sendTelemetryEvent(Telemetry.JupyterKernelSpecified); } + kernelUUID = kernelSelection.kernelId ? kernelSelection.kernelId : undefined; + kernelName = kernelSelection.name ? kernelSelection.name : undefined; + kernelSpecs = kernelSpecs.filter(spec => spec.name === kernelName); + kernelSpec = kernelSpecs.length === 1 ? kernelSpecs[0] : undefined; + } - if (shutdownSelection && shutdownSelection.keepRunning) { - traceInfo('Session will not be shutdown on close'); - allowShutdown = false; - } + if (shutdownSelection && shutdownSelection.keepRunning) { + traceInfo('Session will not be shutdown on close'); + allowShutdown = false; } - await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); await this.configuration.updateSetting('dataScience.jupyterServerKernelId', kernelUUID, undefined, vscode.ConfigurationTarget.Workspace); await this.configuration.updateSetting('dataScience.jupyterServerKernelSpec', kernelSpec, undefined, vscode.ConfigurationTarget.Workspace); await this.configuration.updateSetting('dataScience.jupyterServerAllowKernelShutdown', allowShutdown, undefined, vscode.ConfigurationTarget.Workspace); diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 94aa05ff2fbc..dd431308c1dd 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -164,12 +164,22 @@ export interface IKernelQuickPickItem extends QuickPickItem { kernelId: string | undefined; } +export interface IJupyterServerQuickPickItem extends QuickPickItem { + hostName: string | undefined; + uri: string; +} + export interface IJupyterShutdown { label: string; picked: boolean; keepRunning: boolean; } +export interface IJupyterServer { + hostName: string; + uri: string; +} + export const INotebookImporter = Symbol('INotebookImporter'); export interface INotebookImporter extends Disposable { importFromFile(file: string): Promise; diff --git a/src/datascience-ui/react-common/settingsReactSide.ts b/src/datascience-ui/react-common/settingsReactSide.ts index 6ca9d98a99ba..a9959e8202ed 100644 --- a/src/datascience-ui/react-common/settingsReactSide.ts +++ b/src/datascience-ui/react-common/settingsReactSide.ts @@ -39,6 +39,7 @@ function load() { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/color.test.ts b/src/test/datascience/color.test.ts index 390b36a3e34f..697ecda3e8ca 100644 --- a/src/test/datascience/color.test.ts +++ b/src/test/datascience/color.test.ts @@ -56,6 +56,7 @@ suite('Theme colors', () => { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 79817af8c0d1..163c9bdca7c0 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -410,6 +410,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index 723276cd9098..cfcddb9a1aae 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -77,6 +77,7 @@ suite('DataScience Code Watcher Unit Tests', () => { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index 287be7d938e2..63942882c382 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -543,6 +543,7 @@ suite('Jupyter Execution', async () => { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, diff --git a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts index 44f750ce5ab8..679e4557911a 100644 --- a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts +++ b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts @@ -136,6 +136,7 @@ suite('Interactive window command listener', async () => { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: '', changeDirOnImportExport: true, notebookFileRoot: 'WORKSPACE', diff --git a/src/test/datascience/interactiveWindowTestHelpers.tsx b/src/test/datascience/interactiveWindowTestHelpers.tsx index e2c50c6fe8b2..a1fba53218a7 100644 --- a/src/test/datascience/interactiveWindowTestHelpers.tsx +++ b/src/test/datascience/interactiveWindowTestHelpers.tsx @@ -310,6 +310,7 @@ export function defaultDataScienceSettings(): IDataScienceSettings { jupyterServerAllowKernelShutdown: true, jupyterServerKernelId: '', jupyterServerKernelSpec: undefined, + jupyterServers: undefined, jupyterServerURI: 'local', notebookFileRoot: 'WORKSPACE', changeDirOnImportExport: true, From 21e07d958dab0fd7d12d50084ddfbf1ba87d7929 Mon Sep 17 00:00:00 2001 From: hochshi Date: Tue, 3 Sep 2019 08:54:37 +0300 Subject: [PATCH 11/13] Clear remote settings when user requests a local jupyter. --- src/client/datascience/datascience.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index 5907b3614fdc..e4a8969fc376 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -349,6 +349,9 @@ export class DataScience implements IDataScience { @captureTelemetry(Telemetry.SetJupyterURIToLocal) private async setJupyterURIToLocal(): Promise { await this.configuration.updateSetting('dataScience.jupyterServerURI', Settings.JupyterServerLocalLaunch, undefined, vscode.ConfigurationTarget.Workspace); + await this.configuration.updateSetting('dataScience.jupyterServerKernelId', undefined, undefined, vscode.ConfigurationTarget.Workspace); + await this.configuration.updateSetting('dataScience.jupyterServerKernelSpec', undefined, undefined, vscode.ConfigurationTarget.Workspace); + await this.configuration.updateSetting('dataScience.jupyterServerAllowKernelShutdown', undefined, undefined, vscode.ConfigurationTarget.Workspace); } private async getJupyterKernels(connInfo: IConnection): Promise { From bee8d22aa4bf221f096ffca33536cebecbdb0cfc Mon Sep 17 00:00:00 2001 From: hochshi Date: Wed, 4 Sep 2019 09:41:22 +0300 Subject: [PATCH 12/13] Add requested remote kernel uuid to kernel spec. --- src/client/datascience/datascience.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index e4a8969fc376..98130d362e10 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -456,6 +456,9 @@ export class DataScience implements IDataScience { kernelName = kernelSelection.name ? kernelSelection.name : undefined; kernelSpecs = kernelSpecs.filter(spec => spec.name === kernelName); kernelSpec = kernelSpecs.length === 1 ? kernelSpecs[0] : undefined; + if (kernelSpec) { + kernelSpec.id = kernelUUID; + } } if (shutdownSelection && shutdownSelection.keepRunning) { From 93927d214ae8da618afb09c4af4da8d4cd18595e Mon Sep 17 00:00:00 2001 From: hochshi Date: Wed, 4 Sep 2019 14:00:46 +0300 Subject: [PATCH 13/13] Added news. --- news/1 Enhancements/3763.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/3763.md diff --git a/news/1 Enhancements/3763.md b/news/1 Enhancements/3763.md new file mode 100644 index 000000000000..cc59d7c8d3ad --- /dev/null +++ b/news/1 Enhancements/3763.md @@ -0,0 +1 @@ +Add ability to select remote Jupyter kernel on remote jupyter hosts - @hochshi