Skip to content

Commit

Permalink
Kernel selection UI (#8866)
Browse files Browse the repository at this point in the history
* added the kernel selection UI

* Send message back to react side with
part of the kernel data

* adressed PR comments

* added news file and created a separate message
to change server and kernel

* changed kernel version to display name

added state to jupyter server, still missing states busy and idle

* - localized a hardcoded string
- moved the session event handling to jupyterSession.ts
- moved the server and kernel select dropdown to interactiveBase.ts
- added the kernel select UI to the interactive window

* - added an icon to the kernel selection UI
- added telemetry
- moved kernel selection UI to its own react component
- call the kernelSelector when pressing the UI

* added tests

* fixed tests

* addressed some comments

* only bind statusChanged event on the
jupyterSession once

* Code review comments

* Fix changing kernels to actually change the kernel

* Make sure notebook has kernel before ui does.

* Fix linter and functional test
  • Loading branch information
David Kutugata authored and rchiodo committed Dec 11, 2019
1 parent c9d6887 commit 6cadda5
Show file tree
Hide file tree
Showing 49 changed files with 613 additions and 271 deletions.
1 change: 1 addition & 0 deletions news/1 Enhancements/8866.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added kernel status and selection toolbar to the notebook editor.
3 changes: 3 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@
"DataScience.loadingMessage": "loading ...",
"DataScience.fetchingDataViewer": "Fetching data ...",
"DataScience.noRowsInDataViewer": "No rows match current filter",
"DataScience.jupyterServer": "Jupyter Server",
"DataScience.noKernel": "No Kernel",
"DataScience.localJupyterServer": "local",
"DataScience.pandasTooOldForViewingFormat": "Python package 'pandas' is version {0}. Version 0.20 or greater is required for viewing data.",
"DataScience.pandasRequiredForViewing": "Python package 'pandas' is required for viewing data.",
"DataScience.valuesColumn": "values",
Expand Down
3 changes: 3 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ export namespace DataScience {
export const loadingMessage = localize('DataScience.loadingMessage', 'loading ...');
export const fetchingDataViewer = localize('DataScience.fetchingDataViewer', 'Fetching data ...');
export const noRowsInDataViewer = localize('DataScience.noRowsInDataViewer', 'No rows match current filter');
export const jupyterServer = localize('DataScience.jupyterServer', 'Jupyter Server');
export const noKernel = localize('DataScience.noKernel', 'No Kernel');
export const localJupyterServer = localize('DataScience.localJupyterServer', 'local');
export const pandasTooOldForViewingFormat = localize('DataScience.pandasTooOldForViewingFormat', 'Python package \'pandas\' is version {0}. Version 0.20 or greater is required for viewing data.');
export const pandasRequiredForViewing = localize('DataScience.pandasRequiredForViewing', 'Python package \'pandas\' is required for viewing data.');
export const valuesColumn = localize('DataScience.valuesColumn', 'values');
Expand Down
2 changes: 2 additions & 0 deletions src/client/datascience/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export enum Telemetry {
ExpandAll = 'DATASCIENCE.EXPAND_ALL',
CollapseAll = 'DATASCIENCE.COLLAPSE_ALL',
SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI',
SelectLocalJupyterKernel = 'DATASCIENCE.SELECT_LOCAL_JUPYTER_KERNEL',
SelectRemoteJupyuterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL',
SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL',
SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED',
Interrupt = 'DATASCIENCE.INTERRUPT',
Expand Down
39 changes: 17 additions & 22 deletions src/client/datascience/datascience.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import '../common/extensions';

import { JSONObject } from '@phosphor/coreutils';
import { inject, injectable, multiInject, named, optional } from 'inversify';
import { URL } from 'url';
import * as vscode from 'vscode';

import { ICommandManager, IDebugService, IDocumentManager, IWorkspaceService } from '../common/application/types';
import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants';
import { ContextKey } from '../common/contextKey';
import '../common/extensions';
import { traceError } from '../common/logger';
import {
BANNER_NAME_DS_SURVEY,
GLOBAL_MEMENTO,
IConfigurationService,
IDisposable,
IDisposableRegistry,
IExtensionContext,
IMemento,
IPythonExtensionBanner
} from '../common/types';
import { BANNER_NAME_DS_SURVEY, GLOBAL_MEMENTO, IConfigurationService, IDisposable, IDisposableRegistry, IExtensionContext, IMemento, IPythonExtensionBanner } from '../common/types';
import { waitForPromise } from '../common/utils/async';
import { debounceAsync, swallowExceptions } from '../common/utils/decorators';
import * as localize from '../common/utils/localize';
Expand All @@ -32,14 +21,8 @@ import { captureTelemetry, sendTelemetryEvent } from '../telemetry';
import { hasCells } from './cellFactory';
import { Commands, EditorContexts, Settings, Telemetry } from './constants';
import { createRemoteConnectionInfo } from './jupyter/jupyterUtils';
import {
ICodeWatcher,
IDataScience,
IDataScienceCodeLensProvider,
IDataScienceCommandListener,
IJupyterSessionManagerFactory,
INotebookEditorProvider
} from './types';
import { KernelSelector, KernelSpecInterpreter } from './jupyter/kernels/kernelSelector';
import { ICodeWatcher, IConnection, IDataScience, IDataScienceCodeLensProvider, IDataScienceCommandListener, IJupyterSessionManagerFactory, INotebookEditorProvider } from './types';

interface ISelectUriQuickPickItem extends vscode.QuickPickItem {
newChoice: boolean;
Expand All @@ -66,7 +49,8 @@ export class DataScience implements IDataScience {
@inject(IDebugService) private debugService: IDebugService,
@inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: vscode.Memento,
@inject(IJupyterSessionManagerFactory) private jupyterSessionManagerFactory: IJupyterSessionManagerFactory,
@inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory
@inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory,
@inject(KernelSelector) private kernelSelector: KernelSelector
) {
this.dataScienceSurveyBanner = this.serviceContainer.get<IPythonExtensionBanner>(IPythonExtensionBanner, BANNER_NAME_DS_SURVEY);
}
Expand Down Expand Up @@ -242,6 +226,17 @@ export class DataScience implements IDataScience {
return multiStep.run(this.startSelectingURI.bind(this), {});
}

@captureTelemetry(Telemetry.SelectLocalJupyterKernel)
public async selectLocalJupyterKernel(): Promise<KernelSpecInterpreter> {
return this.kernelSelector.selectLocalKernel();
}

@captureTelemetry(Telemetry.SelectRemoteJupyuterKernel)
public async selectRemoteJupyterKernel(connInfo: IConnection): Promise<KernelSpecInterpreter> {
const session = await this.jupyterSessionManagerFactory.create(connInfo);
return this.kernelSelector.selectRemoteKernel(session);
}

public async debugCell(file: string, startLine: number, startChar: number, endLine: number, endChar: number): Promise<void> {
this.dataScienceSurveyBanner.showBanner().ignoreErrors();

Expand Down
61 changes: 60 additions & 1 deletion src/client/datascience/interactive-common/interactiveBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ConfigurationTarget, Event, EventEmitter, Position, Range, Selection, T
import { Disposable } from 'vscode-jsonrpc';
import * as vsls from 'vsls/vscode';

import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState';
import {
IApplicationShell,
IDocumentManager,
Expand All @@ -31,8 +32,9 @@ import { IInterpreterService, PythonInterpreter } from '../../interpreter/contra
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
import { generateCellRangesFromDocument } from '../cellFactory';
import { CellMatcher } from '../cellMatcher';
import { Identifiers, Telemetry } from '../constants';
import { Identifiers, Settings, Telemetry } from '../constants';
import { ColumnWarningSize } from '../data-viewing/types';
import { DataScience } from '../datascience';
import {
IAddedSysInfo,
ICopyCode,
Expand All @@ -48,6 +50,7 @@ import {
import { JupyterInstallError } from '../jupyter/jupyterInstallError';
import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError';
import { KernelSpecInterpreter } from '../jupyter/kernels/kernelSelector';
import { CssMessages } from '../messages';
import {
CellState,
Expand Down Expand Up @@ -110,6 +113,7 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
@unmanaged() private jupyterVariables: IJupyterVariables,
@unmanaged() private jupyterDebugger: IJupyterDebugger,
@unmanaged() protected ipynbProvider: INotebookEditorProvider,
@unmanaged() private dataScience: DataScience,
@unmanaged() protected errorHandler: IDataScienceErrorHandler,
@unmanaged() rootPath: string,
@unmanaged() scripts: string[],
Expand Down Expand Up @@ -261,6 +265,14 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
this.handleMessage(message, payload, this.requestOnigasm);
break;

case InteractiveWindowMessages.SelectKernel:
this.handleMessage(message, payload, this.selectKernel);
break;

case InteractiveWindowMessages.SelectJupyterServer:
this.handleMessage(message, payload, this.selectServer);
break;

default:
break;
}
Expand Down Expand Up @@ -1068,6 +1080,25 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
if (this.notebook) {
const uri: Uri = await this.getNotebookIdentity();
this.postMessage(InteractiveWindowMessages.NotebookExecutionActivated, uri.toString()).ignoreErrors();

const statusChangeHandler = async (status: ServerStatus) => {
if (this.notebook) {
const settings = this.configuration.getSettings();
const kernelSpec = this.notebook.getKernelSpec();

if (kernelSpec) {
const name = kernelSpec.display_name;

await this.postMessage(InteractiveWindowMessages.UpdateKernel, {
jupyterServerStatus: status,
localizedUri: settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch ?
localize.DataScience.localJupyterServer() : settings.datascience.jupyterServerURI,
displayName: name
});
}
}
};
this.notebook.onSessionStatusChanged(statusChangeHandler);
}

traceInfo('Connected to jupyter server.');
Expand Down Expand Up @@ -1265,4 +1296,32 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
this.postMessage(InteractiveWindowMessages.LoadOnigasmAssemblyResponse, undefined).ignoreErrors();
}
}

private async selectServer() {
await this.dataScience.selectJupyterURI();
}

private async selectKernel() {
const settings = this.configuration.getSettings();

let kernel: KernelSpecInterpreter | undefined;

if (settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch) {
kernel = await this.dataScience.selectLocalJupyterKernel();
} else if (this.notebook) {
const connInfo = this.notebook.server.getConnectionInfo();

if (connInfo) {
kernel = await this.dataScience.selectRemoteJupyterKernel(connInfo);
}
}

if (kernel && kernel.kernelSpec && this.notebook) {
// Tell the kernel. A status update should fire that changes our display
await this.notebook.setKernelSpec(kernel.kernelSpec);

// Add in a new sys info
await this.addSysInfo(SysInfoReason.New);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.
'use strict';
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';

import { IServerState } from '../../../datascience-ui/interactive-common/mainState';
import { CssMessages, IGetCssRequest, IGetCssResponse, IGetMonacoThemeRequest } from '../messages';
import { IGetMonacoThemeResponse } from '../monacoMessages';
import { ICell, IInteractiveWindowInfo, IJupyterVariable, IJupyterVariablesResponse } from '../types';
Expand Down Expand Up @@ -83,7 +83,10 @@ export enum InteractiveWindowMessages {
ExecutionRendered = 'rendered_execution',
FocusedCellEditor = 'focused_cell_editor',
MonacoReady = 'monaco_ready',
ClearAllOutputs = 'clear_all_outputs'
ClearAllOutputs = 'clear_all_outputs',
SelectKernel = 'select_kernel',
UpdateKernel = 'update_kernel',
SelectJupyterServer = 'select_jupyter_server'
}

export enum NativeCommandType {
Expand All @@ -107,6 +110,8 @@ export enum NativeCommandType {
RunAndAdd,
RunAndMove,
RunBelow,
SelectKernel,
SelectServer,
ToggleLineNumbers,
ToggleOutput,
ToggleVariableExplorer,
Expand Down Expand Up @@ -283,6 +288,8 @@ export class IInteractiveWindowMapping {
public [InteractiveWindowMessages.CopyCodeCell]: ICopyCode;
public [InteractiveWindowMessages.NotebookExecutionActivated]: string;
public [InteractiveWindowMessages.RestartKernel]: never | undefined;
public [InteractiveWindowMessages.SelectKernel]: IServerState | undefined;
public [InteractiveWindowMessages.SelectJupyterServer]: never | undefined;
public [InteractiveWindowMessages.Export]: ICell[];
public [InteractiveWindowMessages.GetAllCells]: ICell;
public [InteractiveWindowMessages.ReturnAllCells]: ICell[];
Expand Down Expand Up @@ -354,4 +361,5 @@ export class IInteractiveWindowMapping {
public [InteractiveWindowMessages.FocusedCellEditor]: IFocusedCellEditor;
public [InteractiveWindowMessages.MonacoReady]: never | undefined;
public [InteractiveWindowMessages.ClearAllOutputs]: never | undefined;
public [InteractiveWindowMessages.UpdateKernel]: IServerState | undefined;
}
5 changes: 4 additions & 1 deletion src/client/datascience/interactive-ipynb/nativeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import { IInterpreterService } from '../../interpreter/contracts';
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
import { concatMultilineStringInput, splitMultilineString } from '../common';
import { EditorContexts, Identifiers, NativeKeyboardCommandTelemetryLookup, NativeMouseCommandTelemetryLookup, Telemetry } from '../constants';
import { DataScience } from '../datascience';
import { InteractiveBase } from '../interactive-common/interactiveBase';
import { IEditCell, IInsertCell, INativeCommand, InteractiveWindowMessages, IRemoveCell, ISaveAll, ISubmitNewCell, ISwapCells } from '../interactive-common/interactiveWindowTypes';
import { InvalidNotebookFileError } from '../jupyter/invalidNotebookFileError';
import { CellState, ICell, ICodeCssGenerator, IDataScienceErrorHandler, IDataViewerProvider, IInteractiveWindowInfo, IInteractiveWindowListener, IJupyterDebugger, IJupyterExecution, IJupyterVariables, INotebookEditor, INotebookEditorProvider, INotebookExporter, INotebookImporter, INotebookServerOptions, IStatusProvider, IThemeFinder } from '../types';
import { CellState, ICell, ICodeCssGenerator, IDataScience, IDataScienceErrorHandler, IDataViewerProvider, IInteractiveWindowInfo, IInteractiveWindowListener, IJupyterDebugger, IJupyterExecution, IJupyterVariables, INotebookEditor, INotebookEditorProvider, INotebookExporter, INotebookImporter, INotebookServerOptions, IStatusProvider, IThemeFinder } from '../types';

const nativeEditorDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'native-editor');
enum AskForSaveResult {
Expand Down Expand Up @@ -72,6 +73,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
@inject(IJupyterVariables) jupyterVariables: IJupyterVariables,
@inject(IJupyterDebugger) jupyterDebugger: IJupyterDebugger,
@inject(INotebookImporter) private importer: INotebookImporter,
@inject(IDataScience) dataScience: DataScience,
@inject(IDataScienceErrorHandler) errorHandler: IDataScienceErrorHandler,
@inject(IMemento) @named(GLOBAL_MEMENTO) private globalStorage: Memento,
@inject(IMemento) @named(WORKSPACE_MEMENTO) private localStorage: Memento
Expand All @@ -96,6 +98,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
jupyterVariables,
jupyterDebugger,
editorProvider,
dataScience,
errorHandler,
nativeEditorDir,
[path.join(nativeEditorDir, 'index_bundle.js')],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, IAsyncDisp
const useDefaultConfig: boolean | undefined = settings.datascience.useDefaultConfigForJupyter;

// For the local case pass in our URI as undefined, that way connect doesn't have to check the setting
if (serverURI === Settings.JupyterServerLocalLaunch) {
if (serverURI.toLowerCase() === Settings.JupyterServerLocalLaunch) {
serverURI = undefined;
}

Expand Down
35 changes: 6 additions & 29 deletions src/client/datascience/interactive-window/interactiveWindow.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import '../../common/extensions';

import { inject, injectable, multiInject } from 'inversify';
import * as path from 'path';
import { Event, EventEmitter, TextEditor, Uri, ViewColumn } from 'vscode';

import {
IApplicationShell,
ICommandManager,
IDocumentManager,
ILiveShareApi,
IWebPanelProvider,
IWorkspaceService
} from '../../common/application/types';
import { IApplicationShell, ICommandManager, IDocumentManager, ILiveShareApi, IWebPanelProvider, IWorkspaceService } from '../../common/application/types';
import { ContextKey } from '../../common/contextKey';
import '../../common/extensions';
import { IFileSystem } from '../../common/platform/types';
import { IConfigurationService, IDisposableRegistry, IPersistentStateFactory } from '../../common/types';
import * as localize from '../../common/utils/localize';
import { EXTENSION_ROOT_DIR } from '../../constants';
import { IInterpreterService } from '../../interpreter/contracts';
import { captureTelemetry } from '../../telemetry';
import { EditorContexts, Identifiers, Telemetry } from '../constants';
import { DataScience } from '../datascience';
import { InteractiveBase } from '../interactive-common/interactiveBase';
import { InteractiveWindowMessages, ISubmitNewCell } from '../interactive-common/interactiveWindowTypes';
import {
ICell,
ICodeCssGenerator,
IDataScienceErrorHandler,
IDataViewerProvider,
IInteractiveWindow,
IInteractiveWindowInfo,
IInteractiveWindowListener,
IInteractiveWindowProvider,
IJupyterDebugger,
IJupyterExecution,
IJupyterVariables,
INotebookEditorProvider,
INotebookExporter,
INotebookServerOptions,
IStatusProvider,
IThemeFinder
} from '../types';
import { ICell, ICodeCssGenerator, IDataScience, IDataScienceErrorHandler, IDataViewerProvider, IInteractiveWindow, IInteractiveWindowInfo, IInteractiveWindowListener, IInteractiveWindowProvider, IJupyterDebugger, IJupyterExecution, IJupyterVariables, INotebookEditorProvider, INotebookExporter, INotebookServerOptions, IStatusProvider, IThemeFinder } from '../types';

const historyReactDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'history-react');

Expand Down Expand Up @@ -75,6 +50,7 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi
@inject(IJupyterVariables) jupyterVariables: IJupyterVariables,
@inject(IJupyterDebugger) jupyterDebugger: IJupyterDebugger,
@inject(INotebookEditorProvider) editorProvider: INotebookEditorProvider,
@inject(IDataScience) dataScience: DataScience,
@inject(IDataScienceErrorHandler) errorHandler: IDataScienceErrorHandler,
@inject(IPersistentStateFactory) private readonly stateFactory: IPersistentStateFactory
) {
Expand All @@ -98,6 +74,7 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi
jupyterVariables,
jupyterDebugger,
editorProvider,
dataScience,
errorHandler,
historyReactDir,
[path.join(historyReactDir, 'index_bundle.js')],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class InteractiveWindowProvider implements IInteractiveWindowProvider, IA
const useDefaultConfig: boolean | undefined = settings.datascience.useDefaultConfigForJupyter;

// For the local case pass in our URI as undefined, that way connect doesn't have to check the setting
if (serverURI === Settings.JupyterServerLocalLaunch) {
if (serverURI.toLowerCase() === Settings.JupyterServerLocalLaunch) {
serverURI = undefined;
}

Expand Down
Loading

0 comments on commit 6cadda5

Please sign in to comment.