More Layering (#9139)

* move handling generated files to the serilization classes

* remove unneeded methods

* add more folders to strictire compile, add more strict compile options

* update ci

* wip

* add more layering and fix issues

* add more strictness

* remove unnecessary assertion

* add missing checks

* fix indentation

* wip

* remove jsdoc

* fix layering

* fix compile

* fix compile errors

* wip

* wip

* finish layering

* fix css

* more layering

* rip

* reworking results serializer

* move some files around

* move capabilities to platform wip

* implement capabilities register provider

* fix capabilities service

* fix usage of the regist4ry

* add contribution

* wip

* wip

* wip

* remove no longer good parts

* fix strict-nulls

* fix issues with startup

* another try

* fix startup

* fix imports

* fix tests

* fix tests

* fix more tests

* fix tests

* fix more tests

* fix broken test

* fix tabbing

* fix naming

* wip

* finished layering

* fix imports

* fix valid layers

* fix layers
This commit is contained in:
Anthony Dresser
2020-02-15 01:54:23 -06:00
committed by GitHub
parent 873c6a39fe
commit 506c6a5e5f
338 changed files with 815 additions and 724 deletions

View File

@@ -0,0 +1,718 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { nb, ServerInfo } from 'azdata';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils';
import { CellTypes, CellType, NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts';
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
import { ICellModel, IOutputChangedEvent, CellExecutionState, ICellModelOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors';
import { generateUuid } from 'vs/base/common/uuid';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { firstIndex, find } from 'vs/base/common/arrays';
import { HideInputTag } from 'sql/platform/notebooks/common/outputRegistry';
import { FutureInternal, notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces';
let modelId = 0;
export class CellModel implements ICellModel {
public id: string;
private _cellType: nb.CellType;
private _source: string | string[];
private _language: string;
private _cellGuid: string;
private _future: FutureInternal;
private _outputs: nb.ICellOutput[] = [];
private _isEditMode: boolean;
private _onOutputsChanged = new Emitter<IOutputChangedEvent>();
private _onCellModeChanged = new Emitter<boolean>();
private _onExecutionStateChanged = new Emitter<CellExecutionState>();
private _isTrusted: boolean;
private _active: boolean;
private _hover: boolean;
private _executionCount: number | undefined;
private _cellUri: URI;
private _connectionManagementService: IConnectionManagementService;
private _stdInHandler: nb.MessageHandler<nb.IStdinMessage>;
private _onCellLoaded = new Emitter<string>();
private _loaded: boolean;
private _stdInVisible: boolean;
private _metadata: { language?: string; tags?: string[]; cellGuid?: string; };
private _isCollapsed: boolean;
private _onCollapseStateChanged = new Emitter<boolean>();
private _modelContentChangedEvent: IModelContentChangedEvent;
private readonly _ariaLabel: string;
private readonly codeCellLabel = localize('codeCellLabel', "Code Cell");
private readonly textCellLabel = localize('textCellLabel', "Text Cell");
constructor(cellData: nb.ICellContents,
private _options: ICellModelOptions,
@optional(INotebookService) private _notebookService?: INotebookService
) {
this.id = `${modelId++}`;
if (cellData) {
// Read in contents if available
this.fromJSON(cellData);
} else {
this._cellType = CellTypes.Code;
this._source = '';
}
if (this._cellType === CellTypes.Code) {
this._ariaLabel = this.codeCellLabel;
} else {
this._ariaLabel = this.textCellLabel;
}
this._isEditMode = this._cellType !== CellTypes.Markdown;
this._stdInVisible = false;
if (_options && _options.isTrusted) {
this._isTrusted = true;
} else {
this._isTrusted = false;
}
// if the fromJson() method was already called and _cellGuid was previously set, don't generate another UUID unnecessarily
this._cellGuid = this._cellGuid || generateUuid();
this.createUri();
}
public equals(other: ICellModel) {
return other !== undefined && other.id === this.id;
}
public get ariaLabel(): string {
return this._ariaLabel;
}
public get onCollapseStateChanged(): Event<boolean> {
return this._onCollapseStateChanged.event;
}
public get onOutputsChanged(): Event<IOutputChangedEvent> {
return this._onOutputsChanged.event;
}
public get isEditMode(): boolean {
return this._isEditMode;
}
public get future(): FutureInternal {
return this._future;
}
public get isCollapsed() {
return this._isCollapsed;
}
public set isCollapsed(value: boolean) {
if (this.cellType !== CellTypes.Code) {
return;
}
let stateChanged = this._isCollapsed !== value;
this._isCollapsed = value;
let tagIndex = -1;
if (this._metadata.tags) {
tagIndex = firstIndex(this._metadata.tags, tag => tag === HideInputTag);
}
if (this._isCollapsed) {
if (tagIndex === -1) {
if (!this._metadata.tags) {
this._metadata.tags = [];
}
this._metadata.tags.push(HideInputTag);
}
} else {
if (tagIndex > -1) {
this._metadata.tags.splice(tagIndex, 1);
}
}
if (stateChanged) {
this._onCollapseStateChanged.fire(this._isCollapsed);
this.sendChangeToNotebook(NotebookChangeType.CellInputVisibilityChanged);
}
}
public set isEditMode(isEditMode: boolean) {
this._isEditMode = isEditMode;
this._onCellModeChanged.fire(this._isEditMode);
// Note: this does not require a notebook update as it does not change overall state
}
public get trustedMode(): boolean {
return this._isTrusted;
}
public set trustedMode(isTrusted: boolean) {
if (this._isTrusted !== isTrusted) {
this._isTrusted = isTrusted;
let outputEvent: IOutputChangedEvent = {
outputs: this._outputs,
shouldScroll: false
};
this._onOutputsChanged.fire(outputEvent);
}
}
public get active(): boolean {
return this._active;
}
public set active(value: boolean) {
this._active = value;
this.fireExecutionStateChanged();
}
public get hover(): boolean {
return this._hover;
}
public set hover(value: boolean) {
this._hover = value;
this.fireExecutionStateChanged();
}
public get executionCount(): number | undefined {
return this._executionCount;
}
public set executionCount(value: number | undefined) {
this._executionCount = value;
this.fireExecutionStateChanged();
}
public get cellUri(): URI {
return this._cellUri;
}
public get notebookModel(): NotebookModel {
return this._options && <NotebookModel>this._options.notebook;
}
public set cellUri(value: URI) {
this._cellUri = value;
}
public get cellType(): CellType {
return this._cellType;
}
public get source(): string | string[] {
return this._source;
}
public set source(newSource: string | string[]) {
newSource = this.getMultilineSource(newSource);
if (this._source !== newSource) {
this._source = newSource;
this.sendChangeToNotebook(NotebookChangeType.CellSourceUpdated);
}
this._modelContentChangedEvent = undefined;
}
public get modelContentChangedEvent(): IModelContentChangedEvent {
return this._modelContentChangedEvent;
}
public set modelContentChangedEvent(e: IModelContentChangedEvent) {
this._modelContentChangedEvent = e;
}
public get language(): string {
if (this._cellType === CellTypes.Markdown) {
return 'markdown';
}
if (this._language) {
return this._language;
}
return this._options.notebook.language;
}
public get cellGuid(): string {
return this._cellGuid;
}
public setOverrideLanguage(newLanguage: string) {
this._language = newLanguage;
}
public get onExecutionStateChange(): Event<CellExecutionState> {
return this._onExecutionStateChanged.event;
}
private fireExecutionStateChanged(): void {
this._onExecutionStateChanged.fire(this.executionState);
}
public get onLoaded(): Event<string> {
return this._onCellLoaded.event;
}
public get loaded(): boolean {
return this._loaded;
}
public set loaded(val: boolean) {
this._loaded = val;
if (val) {
this._onCellLoaded.fire(this._cellType);
}
}
public get stdInVisible(): boolean {
return this._stdInVisible;
}
public set stdInVisible(val: boolean) {
this._stdInVisible = val;
}
private notifyExecutionComplete(): void {
if (this._notebookService) {
this._notebookService.serializeNotebookStateChange(this.notebookModel.notebookUri, NotebookChangeType.CellExecuted, this)
.catch(e => onUnexpectedError(e));
}
}
public get executionState(): CellExecutionState {
let isRunning = !!(this._future && this._future.inProgress);
if (isRunning) {
return CellExecutionState.Running;
} else if (this.active || this.hover) {
return CellExecutionState.Stopped;
}
// TODO save error state and show the error
return CellExecutionState.Hidden;
}
public async runCell(notificationService?: INotificationService, connectionManagementService?: IConnectionManagementService): Promise<boolean> {
try {
if (!this.active && this !== this.notebookModel.activeCell) {
this.notebookModel.updateActiveCell(this);
this.active = true;
}
if (connectionManagementService) {
this._connectionManagementService = connectionManagementService;
}
if (this.cellType !== CellTypes.Code) {
// TODO should change hidden state to false if we add support
// for this property
return false;
}
let kernel = await this.getOrStartKernel(notificationService);
if (!kernel) {
return false;
}
// If cell is currently running and user clicks the stop/cancel button, call kernel.interrupt()
// This matches the same behavior as JupyterLab
if (this.future && this.future.inProgress) {
// If stdIn is visible, to prevent a kernel hang, we need to send a dummy input reply
if (this._stdInVisible && this._stdInHandler) {
this.future.sendInputReply({ value: '' });
}
this.future.inProgress = false;
await kernel.interrupt();
this.sendNotification(notificationService, Severity.Info, localize('runCellCancelled', "Cell execution cancelled"));
} else {
// TODO update source based on editor component contents
if (kernel.requiresConnection && !this.notebookModel.context) {
let connected = await this.notebookModel.requestConnection();
if (!connected) {
return false;
}
}
let content = this.source;
if ((Array.isArray(content) && content.length > 0) || (!Array.isArray(content) && content)) {
this.notebookModel.trustedMode = true;
// requestExecute expects a string for the code parameter
content = Array.isArray(content) ? content.join('') : content;
const future = kernel.requestExecute({
code: content,
stop_on_error: true
}, false);
this.setFuture(future as FutureInternal);
this.fireExecutionStateChanged();
// For now, await future completion. Later we should just track and handle cancellation based on model notifications
let result: nb.IExecuteReplyMsg = <nb.IExecuteReplyMsg><any>await future.done;
if (result && result.content) {
this.executionCount = result.content.execution_count;
if (result.content.status !== 'ok') {
// TODO track error state
return false;
}
}
}
}
} catch (error) {
let message: string;
if (error.message === 'Canceled') {
message = localize('executionCanceled', "Query execution was canceled");
} else {
message = getErrorMessage(error);
}
this.sendNotification(notificationService, Severity.Error, message);
// TODO track error state for the cell
} finally {
this.disposeFuture();
this.fireExecutionStateChanged();
this.notifyExecutionComplete();
}
return true;
}
private async getOrStartKernel(notificationService: INotificationService): Promise<nb.IKernel> {
let model = this._options.notebook;
let clientSession = model && model.clientSession;
if (!clientSession) {
this.sendNotification(notificationService, Severity.Error, localize('notebookNotReady', "The session for this notebook is not yet ready"));
return undefined;
} else if (!clientSession.isReady || clientSession.status === 'dead') {
this.sendNotification(notificationService, Severity.Info, localize('sessionNotReady', "The session for this notebook will start momentarily"));
await clientSession.kernelChangeCompleted;
}
if (!clientSession.kernel) {
let defaultKernel = model && model.defaultKernel && model.defaultKernel.name;
if (!defaultKernel) {
this.sendNotification(notificationService, Severity.Error, localize('noDefaultKernel', "No kernel is available for this notebook"));
return undefined;
}
await clientSession.changeKernel({
name: defaultKernel
});
}
return clientSession.kernel;
}
private sendNotification(notificationService: INotificationService, severity: Severity, message: string): void {
if (notificationService) {
notificationService.notify({ severity: severity, message: message });
}
}
/**
* Sets the future which will be used to update the output
* area for this cell
*/
setFuture(future: FutureInternal): void {
if (this._future === future) {
// Nothing to do
return;
}
// Setting the future indicates the cell is running which enables trusted mode.
// See https://jupyter-notebook.readthedocs.io/en/stable/security.html
this._isTrusted = true;
if (this._future) {
this._future.dispose();
}
this.clearOutputs();
this._future = future;
future.setReplyHandler({ handle: (msg) => this.handleReply(msg) });
future.setIOPubHandler({ handle: (msg) => this.handleIOPub(msg) });
future.setStdInHandler({ handle: (msg) => this.handleSdtIn(msg) });
}
public clearOutputs(): void {
this._outputs = [];
this.fireOutputsChanged();
this.executionCount = undefined;
}
private fireOutputsChanged(shouldScroll: boolean = false): void {
let outputEvent: IOutputChangedEvent = {
outputs: this.outputs,
shouldScroll: !!shouldScroll
};
this._onOutputsChanged.fire(outputEvent);
if (this.outputs.length !== 0) {
this.sendChangeToNotebook(NotebookChangeType.CellOutputUpdated);
} else {
this.sendChangeToNotebook(NotebookChangeType.CellOutputCleared);
}
}
private sendChangeToNotebook(change: NotebookChangeType): void {
if (this._options && this._options.notebook) {
this._options.notebook.onCellChange(this, change);
}
}
public get outputs(): Array<nb.ICellOutput> {
return this._outputs;
}
private handleReply(msg: nb.IShellMessage): void {
// TODO #931 we should process this. There can be a payload attached which should be added to outputs.
// In all other cases, it is a no-op
if (!this._future.inProgress) {
this.disposeFuture();
}
}
private handleIOPub(msg: nb.IIOPubMessage): void {
let msgType = msg.header.msg_type;
let output: nb.ICellOutput;
switch (msgType) {
case 'execute_result':
case 'display_data':
case 'stream':
case 'error':
output = msg.content as nb.ICellOutput;
output.output_type = msgType;
break;
case 'clear_output':
// TODO wait until next message before clearing
// let wait = (msg as KernelMessage.IClearOutputMsg).content.wait;
this.clearOutputs();
break;
case 'update_display_data':
output = msg.content as nb.ICellOutput;
output.output_type = 'display_data';
// TODO #930 handle in-place update of displayed data
// targets = this._displayIdMap.get(displayId);
// if (targets) {
// for (let index of targets) {
// model.set(index, output);
// }
// }
break;
default:
break;
}
// TODO handle in-place update of displayed data
// if (displayId && msgType === 'display_data') {
// targets = this._displayIdMap.get(displayId) || [];
// targets.push(model.length - 1);
// this._displayIdMap.set(displayId, targets);
// }
if (output) {
// deletes transient node in the serialized JSON
delete output['transient'];
this._outputs.push(this.rewriteOutputUrls(output));
// Only scroll on 1st output being added
let shouldScroll = this._outputs.length === 1;
this.fireOutputsChanged(shouldScroll);
}
}
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
const driverLog = '/gateway/default/yarn/container';
const yarnUi = '/gateway/default/yarn/proxy';
const defaultPort = ':30433';
// Only rewrite if this is coming back during execution, not when loading from disk.
// A good approximation is that the model has a future (needed for execution)
if (this.future) {
try {
let result = output as nb.IDisplayResult;
if (result && result.data && result.data['text/html']) {
let model = this._options.notebook as NotebookModel;
if (model.context) {
let gatewayEndpointInfo = this.getGatewayEndpoint(model.context);
if (gatewayEndpointInfo) {
let hostAndIp = notebookUtils.getHostAndPortFromEndpoint(gatewayEndpointInfo.endpoint);
let host = hostAndIp.host ? hostAndIp.host : model.context.serverName;
let port = hostAndIp.port ? ':' + hostAndIp.port : defaultPort;
let html = result.data['text/html'];
// CTP 3.1 and earlier Spark link
html = this.rewriteUrlUsingRegex(/(https?:\/\/master.*\/proxy)(.*)/g, html, host, port, yarnUi);
// CTP 3.2 and later spark link
html = this.rewriteUrlUsingRegex(/(https?:\/\/sparkhead.*\/proxy)(.*)/g, html, host, port, yarnUi);
// Driver link
html = this.rewriteUrlUsingRegex(/(https?:\/\/storage.*\/containerlogs)(.*)/g, html, host, port, driverLog);
(<nb.IDisplayResult>output).data['text/html'] = html;
}
}
}
}
catch (e) { }
}
return output;
}
private rewriteUrlUsingRegex(regex: RegExp, html: string, host: string, port: string, target: string): string {
return html.replace(regex, function (a, b, c) {
let ret = '';
if (b !== '') {
ret = 'https://' + host + port + target;
}
if (c !== '') {
ret = ret + c;
}
return ret;
});
}
public setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
this._stdInHandler = handler;
}
/**
* StdIn requires user interaction, so this is deferred to upstream UI
* components. If one is registered the cell will call and wait on it, if not
* it will immediately return to unblock error handling
*/
private handleSdtIn(msg: nb.IStdinMessage): void | Thenable<void> {
let handler = async () => {
if (!this._stdInHandler) {
// No-op
return;
}
try {
await this._stdInHandler.handle(msg);
} catch (err) {
if (this.future) {
// TODO should we error out in this case somehow? E.g. send Ctrl+C?
this.future.sendInputReply({ value: '' });
}
}
};
return handler();
}
public toJSON(): nb.ICellContents {
let metadata = this._metadata || {};
let cellJson: Partial<nb.ICellContents> = {
cell_type: this._cellType,
source: this._source,
metadata: metadata
};
cellJson.metadata.azdata_cell_guid = this._cellGuid;
if (this._cellType === CellTypes.Code) {
cellJson.metadata.language = this._language;
cellJson.metadata.tags = metadata.tags;
cellJson.outputs = this._outputs;
cellJson.execution_count = this.executionCount ? this.executionCount : null;
}
return cellJson as nb.ICellContents;
}
public fromJSON(cell: nb.ICellContents): void {
if (!cell) {
return;
}
this._cellType = cell.cell_type;
this.executionCount = cell.execution_count;
this._source = this.getMultilineSource(cell.source);
this._metadata = cell.metadata || {};
if (this._metadata.tags && this._metadata.tags.some(x => x === HideInputTag) && this._cellType === CellTypes.Code) {
this._isCollapsed = true;
} else {
this._isCollapsed = false;
}
this._cellGuid = cell.metadata && cell.metadata.azdata_cell_guid ? cell.metadata.azdata_cell_guid : generateUuid();
this.setLanguageFromContents(cell);
if (cell.outputs) {
for (let output of cell.outputs) {
// For now, we're assuming it's OK to save these as-is with no modification
this.addOutput(output);
}
}
}
private setLanguageFromContents(cell: nb.ICellContents): void {
if (cell.cell_type === CellTypes.Markdown) {
this._language = 'markdown';
} else if (cell.metadata && cell.metadata.language) {
this._language = cell.metadata.language;
}
// else skip, we set default language anyhow
}
private addOutput(output: nb.ICellOutput) {
this._normalize(output);
this._outputs.push(output);
}
/**
* Normalize an output.
*/
private _normalize(value: nb.ICellOutput): void {
if (notebookUtils.isStream(value)) {
if (Array.isArray(value.text)) {
value.text = (value.text as string[]).join('\n');
}
}
}
private createUri(): void {
let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.id}` });
// Use this to set the internal (immutable) and public (shared with extension) uri properties
this.cellUri = uri;
}
// Get Knox endpoint from IConnectionProfile
// TODO: this will be refactored out into the notebooks extension as a contribution point
private getGatewayEndpoint(activeConnection: IConnectionProfile): notebookUtils.IEndpoint {
let endpoint;
if (this._connectionManagementService && activeConnection && activeConnection.providerName.toLowerCase() === notebookConstants.SQL_CONNECTION_PROVIDER.toLowerCase()) {
let serverInfo: ServerInfo = this._connectionManagementService.getServerInfo(activeConnection.id);
if (serverInfo) {
let endpoints: notebookUtils.IEndpoint[] = notebookUtils.getClusterEndpoints(serverInfo);
if (endpoints && endpoints.length > 0) {
endpoint = find(endpoints, ep => ep.serviceName.toLowerCase() === notebookUtils.hadoopEndpointNameGateway);
}
}
}
return endpoint;
}
private getMultilineSource(source: string | string[]): string | string[] {
if (source === undefined) {
return [];
}
if (typeof source === 'string') {
let sourceMultiline = source.split('\n');
// If source is one line (i.e. no '\n'), return it immediately
if (sourceMultiline.length === 1) {
return [source];
} else if (sourceMultiline.length === 0) {
return [];
}
// Otherwise, add back all of the newlines here
// Note: for Windows machines that require '/r/n',
// splitting on '\n' and putting back the '\n' will still
// retain the '\r', so that isn't lost in the process
// Note: the last line will not include a newline at the end
for (let i = 0; i < sourceMultiline.length - 1; i++) {
sourceMultiline[i] += '\n';
}
return sourceMultiline;
}
return source;
}
// Dispose and set current future to undefined
private disposeFuture() {
if (this._future) {
this._future.dispose();
}
this._future = undefined;
}
}

View File

@@ -0,0 +1,366 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This code is based on @jupyterlab/packages/apputils/src/clientsession.tsx
import { nb } from 'azdata';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { localize } from 'vs/nls';
import { getErrorMessage } from 'vs/base/common/errors';
import { IClientSession, IKernelPreference, IClientSessionOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { Deferred } from 'sql/base/common/promise';
import { INotebookManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
type KernelChangeHandler = (kernel: nb.IKernelChangedArgs) => Promise<void>;
/**
* Implementation of a client session. This is a model over session operations,
* which may come from the session manager or a specific session.
*/
export class ClientSession implements IClientSession {
//#region private fields with public accessors
private _terminatedEmitter = new Emitter<void>();
private _kernelChangedEmitter = new Emitter<nb.IKernelChangedArgs>();
private _statusChangedEmitter = new Emitter<nb.ISession>();
private _iopubMessageEmitter = new Emitter<nb.IMessage>();
private _unhandledMessageEmitter = new Emitter<nb.IMessage>();
private _propertyChangedEmitter = new Emitter<'path' | 'name' | 'type'>();
private _notebookUri: URI;
private _type: string;
private _name: string;
private _isReady: boolean;
private _ready: Deferred<void>;
private _kernelChangeCompleted: Deferred<void>;
private _kernelPreference: IKernelPreference;
private _kernelDisplayName: string;
private _errorMessage: string;
private _cachedKernelSpec: nb.IKernelSpec;
private _kernelChangeHandlers: KernelChangeHandler[] = [];
private _defaultKernel: nb.IKernelSpec;
//#endregion
private _serverLoadFinished: Promise<void>;
private _session: nb.ISession;
private isServerStarted: boolean;
private notebookManager: INotebookManager;
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
constructor(private options: IClientSessionOptions) {
this._notebookUri = options.notebookUri;
this.notebookManager = options.notebookManager;
this._isReady = false;
this._ready = new Deferred<void>();
this._kernelChangeCompleted = new Deferred<void>();
this._defaultKernel = options.kernelSpec;
}
public async initialize(): Promise<void> {
try {
this._serverLoadFinished = this.startServer();
await this._serverLoadFinished;
await this.initializeSession();
await this.updateCachedKernelSpec();
} catch (err) {
this._errorMessage = getErrorMessage(err) || localize('clientSession.unknownError', "An error occurred while starting the notebook session");
}
// Always resolving for now. It's up to callers to check for error case
this._isReady = true;
this._ready.resolve();
if (!this.isInErrorState && this._session && this._session.kernel) {
await this.notifyKernelChanged(undefined, this._session.kernel);
}
}
private async startServer(): Promise<void> {
let serverManager = this.notebookManager.serverManager;
if (serverManager) {
await serverManager.startServer();
if (!serverManager.isStarted) {
throw new Error(localize('ServerNotStarted', "Server did not start for unknown reason"));
}
this.isServerStarted = serverManager.isStarted;
} else {
this.isServerStarted = true;
}
}
private async initializeSession(): Promise<void> {
await this._serverLoadFinished;
if (this.isServerStarted) {
if (!this.notebookManager.sessionManager.isReady) {
await this.notebookManager.sessionManager.ready;
}
if (this._defaultKernel) {
await this.startSessionInstance(this._defaultKernel.name);
}
}
}
private async startSessionInstance(kernelName: string): Promise<void> {
let session: nb.ISession;
try {
// TODO #3164 should use URI instead of path for startNew
session = await this.notebookManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: kernelName
// TODO add kernel name if saved in the document
});
session.defaultKernelLoaded = true;
} catch (err) {
// TODO move registration
if (err && err.response && err.response.status === 501) {
this.options.notificationService.warn(localize('kernelRequiresConnection', "Kernel {0} was not found. The default kernel will be used instead.", kernelName));
session = await this.notebookManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: undefined
});
session.defaultKernelLoaded = false;
} else {
throw err;
}
}
this._session = session;
await this.runKernelConfigActions(kernelName);
this._statusChangedEmitter.fire(session);
}
private async runKernelConfigActions(kernelName: string): Promise<void> {
for (let startAction of this._kernelConfigActions) {
await startAction(kernelName);
}
}
public dispose(): void {
// No-op for now
}
/**
* Indicates the server has finished loading. It may have failed to load in
* which case the view will be in an error state.
*/
public get serverLoadFinished(): Promise<void> {
return this._serverLoadFinished;
}
//#region IClientSession Properties
public get terminated(): Event<void> {
return this._terminatedEmitter.event;
}
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
return this._kernelChangedEmitter.event;
}
public onKernelChanging(changeHandler: (kernel: nb.IKernelChangedArgs) => Promise<void>): void {
if (changeHandler) {
this._kernelChangeHandlers.push(changeHandler);
}
}
public get statusChanged(): Event<nb.ISession> {
return this._statusChangedEmitter.event;
}
public get iopubMessage(): Event<nb.IMessage> {
return this._iopubMessageEmitter.event;
}
public get unhandledMessage(): Event<nb.IMessage> {
return this._unhandledMessageEmitter.event;
}
public get propertyChanged(): Event<'path' | 'name' | 'type'> {
return this._propertyChangedEmitter.event;
}
public get kernel(): nb.IKernel | null {
return this._session ? this._session.kernel : undefined;
}
public get notebookUri(): URI {
return this._notebookUri;
}
public get name(): string {
return this._name;
}
public get type(): string {
return this._type;
}
public get status(): nb.KernelStatus {
if (!this.isReady) {
return 'starting';
}
return this._session ? this._session.status : 'dead';
}
public get isReady(): boolean {
return this._isReady;
}
public get ready(): Promise<void> {
return this._ready.promise;
}
public get kernelChangeCompleted(): Promise<void> {
return this._kernelChangeCompleted.promise;
}
public get kernelPreference(): IKernelPreference {
return this._kernelPreference;
}
public set kernelPreference(value: IKernelPreference) {
this._kernelPreference = value;
}
public get kernelDisplayName(): string {
return this._kernelDisplayName;
}
public get errorMessage(): string {
return this._errorMessage;
}
public get isInErrorState(): boolean {
return !!this._errorMessage;
}
public get cachedKernelSpec(): nb.IKernelSpec {
return this._cachedKernelSpec;
}
//#endregion
//#region Not Yet Implemented
/**
* Change the current kernel associated with the document.
*/
async changeKernel(options: nb.IKernelSpec, oldValue?: nb.IKernel): Promise<nb.IKernel> {
this._kernelChangeCompleted = new Deferred<void>();
this._isReady = false;
let oldKernel = oldValue ? oldValue : this.kernel;
let kernel = await this.doChangeKernel(options);
try {
await kernel.ready;
} catch (error) {
// Cleanup some state before re-throwing
this._isReady = kernel.isReady;
this._kernelChangeCompleted.resolve();
throw error;
}
let newKernel = this._session ? kernel : this._session.kernel;
this._isReady = kernel.isReady;
await this.updateCachedKernelSpec();
// Send resolution events to listeners
await this.notifyKernelChanged(oldKernel, newKernel);
return kernel;
}
private async notifyKernelChanged(oldKernel: nb.IKernel, newKernel: nb.IKernel): Promise<void> {
let changeArgs: nb.IKernelChangedArgs = {
oldValue: oldKernel,
newValue: newKernel
};
let changePromises = this._kernelChangeHandlers.map(handler => handler(changeArgs));
await Promise.all(changePromises);
// Wait on connection configuration to complete before resolving full kernel change
this._kernelChangeCompleted.resolve();
this._kernelChangedEmitter.fire(changeArgs);
}
private async updateCachedKernelSpec(): Promise<void> {
this._cachedKernelSpec = undefined;
let kernel = this.kernel;
if (kernel) {
await kernel.ready;
if (kernel.isReady) {
this._cachedKernelSpec = await kernel.getSpec();
}
}
}
/**
* Helper method to either call ChangeKernel on current session, or start a new session
*/
private async doChangeKernel(options: nb.IKernelSpec): Promise<nb.IKernel> {
let kernel: nb.IKernel;
if (this._session) {
kernel = await this._session.changeKernel(options);
await this.runKernelConfigActions(kernel.name);
} else {
kernel = await this.startSessionInstance(options.name).then(() => this.kernel);
}
return kernel;
}
public async configureKernel(options: nb.IKernelSpec): Promise<void> {
if (this._session) {
await this._session.configureKernel(options);
}
}
public async updateConnection(connection: IConnectionProfile): Promise<void> {
if (!this.kernel) {
// TODO is there any case where skipping causes errors? So far it seems like it gets called twice
return;
}
if (connection.id !== '-1') {
await this._session.configureConnection(connection);
}
}
/**
* Kill the kernel and shutdown the session.
*
* @returns A promise that resolves when the session is shut down.
*/
public async shutdown(): Promise<void> {
// Always try to shut down session
if (this._session && this._session.id && this.notebookManager && this.notebookManager.sessionManager) {
await this.notebookManager.sessionManager.shutdown(this._session.id);
}
}
/**
* Select a kernel for the session.
*/
selectKernel(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Restart the session.
*
* @returns A promise that resolves with whether the kernel has restarted.
*
* #### Notes
* If there is a running kernel, present a dialog.
* If there is no kernel, we start a kernel with the last run
* kernel name and resolves with `true`. If no kernel has been started,
* this is a no-op, and resolves with `false`.
*/
restart(): Promise<boolean> {
throw new Error('Not implemented');
}
/**
* Change the session path.
*
* @param path - The new session path.
*
* @returns A promise that resolves when the session has renamed.
*
* #### Notes
* This uses the Jupyter REST API, and the response is validated.
* The promise is fulfilled on a valid response and rejected otherwise.
*/
setPath(path: string): Promise<void> {
throw new Error('Not implemented');
}
/**
* Change the session name.
*/
setName(name: string): Promise<void> {
throw new Error('Not implemented');
}
/**
* Change the session type.
*/
setType(type: string): Promise<void> {
throw new Error('Not implemented');
}
//#endregion
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { nb } from 'azdata';
import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell';
import { IClientSession, IClientSessionOptions, ICellModelOptions, ICellModel, IModelFactory } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { ClientSession } from 'sql/workbench/services/notebook/browser/models/clientSession';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ModelFactory implements IModelFactory {
constructor(private instantiationService: IInstantiationService) {
}
public createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel {
return this.instantiationService.createInstance(CellModel, cell, options);
}
public createClientSession(options: IClientSessionOptions): IClientSession {
return new ClientSession(options);
}
}

View File

@@ -0,0 +1,543 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This code is based on @jupyterlab/packages/apputils/src/clientsession.tsx
import { nb } from 'azdata';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CellType, NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts';
import { INotebookManager, ILanguageMagic } from 'sql/workbench/services/notebook/browser/notebookService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import type { FutureInternal } from 'sql/workbench/services/notebook/browser/interfaces';
export interface IClientSessionOptions {
notebookUri: URI;
notebookManager: INotebookManager;
notificationService: INotificationService;
kernelSpec: nb.IKernelSpec;
}
/**
* The interface of client session object.
*
* The client session represents the link between
* a path and its kernel for the duration of the lifetime
* of the session object. The session can have no current
* kernel, and can start a new kernel at any time.
*/
export interface IClientSession extends IDisposable {
/**
* A signal emitted when the session is shut down.
*/
readonly terminated: Event<void>;
/**
* A signal emitted when the kernel changes.
*/
readonly kernelChanged: Event<nb.IKernelChangedArgs>;
/**
* A signal emitted when the kernel status changes.
*/
readonly statusChanged: Event<nb.ISession>;
/**
* A signal emitted for a kernel messages.
*/
readonly iopubMessage: Event<nb.IMessage>;
/**
* A signal emitted for an unhandled kernel message.
*/
readonly unhandledMessage: Event<nb.IMessage>;
/**
* A signal emitted when a session property changes.
*/
readonly propertyChanged: Event<'path' | 'name' | 'type'>;
/**
* The current kernel associated with the document.
*/
readonly kernel: nb.IKernel | null;
/**
* The current path associated with the client session.
*/
readonly notebookUri: URI;
/**
* The current name associated with the client session.
*/
readonly name: string;
/**
* The type of the client session.
*/
readonly type: string;
/**
* The current status of the client session.
*/
readonly status: nb.KernelStatus;
/**
* Whether the session is ready.
*/
readonly isReady: boolean;
/**
* Whether the session is in an unusable state
*/
readonly isInErrorState: boolean;
/**
* The error information, if this session is in an error state
*/
readonly errorMessage: string;
/**
* A promise that is fulfilled when the session is ready.
*/
readonly ready: Promise<void>;
/**
* A promise that is fulfilled when the session completes a kernel change.
*/
readonly kernelChangeCompleted: Promise<void>;
/**
* The kernel preference.
*/
kernelPreference: IKernelPreference;
/**
* The display name of the kernel.
*/
readonly kernelDisplayName: string;
readonly cachedKernelSpec: nb.IKernelSpec;
/**
* Initializes the ClientSession, by starting the server and
* connecting to the SessionManager.
* This will optionally start a session if the kernel preferences
* indicate this is desired
*/
initialize(): Promise<void>;
/**
* Change the current kernel associated with the document.
*/
changeKernel(
options: nb.IKernelSpec,
oldKernel?: nb.IKernel
): Promise<nb.IKernel>;
/**
* Configure the current kernel associated with the document.
*/
configureKernel(
options: nb.IKernelSpec
): Promise<void>;
/**
* Kill the kernel and shutdown the session.
*
* @returns A promise that resolves when the session is shut down.
*/
shutdown(): Promise<void>;
/**
* Select a kernel for the session.
*/
selectKernel(): Promise<void>;
/**
* Restart the session.
*
* @returns A promise that resolves with whether the kernel has restarted.
*
* #### Notes
* If there is a running kernel, present a dialog.
* If there is no kernel, we start a kernel with the last run
* kernel name and resolves with `true`. If no kernel has been started,
* this is a no-op, and resolves with `false`.
*/
restart(): Promise<boolean>;
/**
* Change the session path.
*
* @param path - The new session path.
*
* @returns A promise that resolves when the session has renamed.
*
* #### Notes
* This uses the Jupyter REST API, and the response is validated.
* The promise is fulfilled on a valid response and rejected otherwise.
*/
setPath(path: string): Promise<void>;
/**
* Change the session name.
*/
setName(name: string): Promise<void>;
/**
* Change the session type.
*/
setType(type: string): Promise<void>;
/**
* Updates the connection
*/
updateConnection(connection: IConnectionProfile): Promise<void>;
/**
* Supports registering a handler to run during kernel change and implement any calls needed to configure
* the kernel before actions such as run should be allowed
*/
onKernelChanging(changeHandler: ((kernel: nb.IKernelChangedArgs) => Promise<void>)): void;
}
/**
* A kernel preference.
*/
export interface IKernelPreference {
/**
* The name of the kernel.
*/
readonly name?: string;
/**
* The preferred kernel language.
*/
readonly language?: string;
/**
* The id of an existing kernel.
*/
readonly id?: string;
/**
* Whether to prefer starting a kernel.
*/
readonly shouldStart?: boolean;
/**
* Whether a kernel can be started.
*/
readonly canStart?: boolean;
/**
* Whether to auto-start the default kernel if no matching kernel is found.
*/
readonly autoStartDefault?: boolean;
}
export interface INotebookModel {
/**
* Cell List for this model
*/
readonly cells: ReadonlyArray<ICellModel>;
/**
* The active cell for this model. May be undefined
*/
activeCell: ICellModel | undefined;
/**
* Client Session in the notebook, used for sending requests to the notebook service
*/
readonly clientSession: IClientSession;
/**
* LanguageInfo saved in the notebook
*/
readonly languageInfo: nb.ILanguageInfo;
/**
* Current default language for the notebook
*/
readonly language: string;
/**
* All notebook managers applicable for a given notebook
*/
readonly notebookManagers: INotebookManager[];
/**
* Event fired on first initialization of the kernel and
* on subsequent change events
*/
readonly kernelChanged: Event<nb.IKernelChangedArgs>;
/**
* Fired on notifications that notebook components should be re-laid out.
*/
readonly layoutChanged: Event<void>;
/**
* Event fired on first initialization of the kernels and
* on subsequent change events
*/
readonly kernelsChanged: Event<nb.IKernelSpec>;
/**
* Default kernel
*/
defaultKernel?: nb.IKernelSpec;
/**
* Event fired on first initialization of the contexts and
* on subsequent change events
*/
readonly contextsChanged: Event<void>;
/**
* Event fired on when switching kernel and should show loading context
*/
readonly contextsLoading: Event<void>;
/**
* The specs for available kernels, or undefined if these have
* not been loaded yet
*/
readonly specs: nb.IAllKernels | undefined;
/**
* The specs for available context, or undefined if this has
* not been loaded yet
*/
readonly context: ConnectionProfile | undefined;
/**
* Event fired on first initialization of the cells and
* on subsequent change events
*/
readonly contentChanged: Event<NotebookContentChange>;
/**
* Event fired on notebook provider change
*/
readonly onProviderIdChange: Event<string>;
/**
* Event fired on active cell change
*/
readonly onActiveCellChanged: Event<ICellModel>;
/**
* The trusted mode of the Notebook
*/
trustedMode: boolean;
/**
* Current notebook provider id
*/
providerId: string;
/**
* Change the current kernel from the Kernel dropdown
* @param displayName kernel name (as displayed in Kernel dropdown)
*/
changeKernel(displayName: string): void;
/**
* Change the current context (if applicable)
*/
changeContext(host: string, connection?: IConnectionProfile, hideErrorMessage?: boolean): Promise<void>;
/**
* Find a cell's index given its model
*/
findCellIndex(cellModel: ICellModel): number;
/**
* Adds a cell to the index of the model
*/
addCell(cellType: CellType, index?: number): void;
/**
* Deletes a cell
*/
deleteCell(cellModel: ICellModel): void;
/**
* Serialize notebook cell content to JSON
*/
toJSON(type?: NotebookChangeType): nb.INotebookContents;
/**
* Notifies the notebook of a change in the cell
*/
onCellChange(cell: ICellModel, change: NotebookChangeType): void;
/**
* Push edit operations, basically editing the model. This is the preferred way of
* editing the model. Long-term, this will ensure edit operations can be added to the undo stack
* @param edits The edit operations to perform
*/
pushEditOperations(edits: ISingleNotebookEditOperation[]): void;
getApplicableConnectionProviderIds(kernelName: string): string[];
updateActiveCell(cell: ICellModel): void;
/**
* Get the standardKernelWithProvider by name
* @param name The kernel name
*/
getStandardKernelFromName(name: string): IStandardKernelWithProvider;
/** Event fired once we get call back from ConfigureConnection method in sqlops extension */
readonly onValidConnectionSelected: Event<boolean>;
serializationStateChanged(changeType: NotebookChangeType, cell?: ICellModel): void;
standardKernels: IStandardKernelWithProvider[];
requestConnection(): Promise<boolean>;
}
export interface NotebookContentChange {
/**
* The type of change that occurred
*/
changeType: NotebookChangeType;
/**
* Optional cells that were changed
*/
cells?: ICellModel | ICellModel[];
/**
* Optional index of the change, indicating the cell at which an insert or
* delete occurred
*/
cellIndex?: number;
/**
* Optional value indicating if the notebook is in a dirty or clean state after this change
*/
isDirty?: boolean;
/**
* Text content changed event for cell edits
*/
modelContentChangedEvent?: IModelContentChangedEvent;
}
export interface ICellModelOptions {
notebook: INotebookModel;
isTrusted: boolean;
}
export enum CellExecutionState {
Hidden = 0,
Stopped = 1,
Running = 2,
Error = 3
}
export interface IOutputChangedEvent {
outputs: ReadonlyArray<nb.ICellOutput>;
shouldScroll: boolean;
}
export interface ICellModel {
cellUri: URI;
id: string;
readonly language: string;
readonly cellGuid: string;
source: string | string[];
cellType: CellType;
trustedMode: boolean;
active: boolean;
hover: boolean;
executionCount: number | undefined;
readonly future: FutureInternal;
readonly outputs: ReadonlyArray<nb.ICellOutput>;
readonly onOutputsChanged: Event<IOutputChangedEvent>;
readonly onExecutionStateChange: Event<CellExecutionState>;
readonly executionState: CellExecutionState;
readonly notebookModel: NotebookModel;
setFuture(future: FutureInternal): void;
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void;
runCell(notificationService?: INotificationService, connectionManagementService?: IConnectionManagementService): Promise<boolean>;
setOverrideLanguage(language: string);
equals(cellModel: ICellModel): boolean;
toJSON(): nb.ICellContents;
loaded: boolean;
stdInVisible: boolean;
readonly onLoaded: Event<string>;
isCollapsed: boolean;
readonly onCollapseStateChanged: Event<boolean>;
modelContentChangedEvent: IModelContentChangedEvent;
isEditMode: boolean;
readonly ariaLabel: string;
}
export interface IModelFactory {
createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel;
createClientSession(options: IClientSessionOptions): IClientSession;
}
export interface IContentManager {
/**
* This is a specialized method intended to load for a default context - just the current Notebook's URI
*/
loadContent(): Promise<nb.INotebookContents>;
}
export interface INotebookModelOptions {
/**
* Path to the local or remote notebook
*/
notebookUri: URI;
/**
* Factory for creating cells and client sessions
*/
factory: IModelFactory;
contentManager: IContentManager;
notebookManagers: INotebookManager[];
providerId: string;
defaultKernel: nb.IKernelSpec;
cellMagicMapper: ICellMagicMapper;
layoutChanged: Event<void>;
notificationService: INotificationService;
connectionService: IConnectionManagementService;
capabilitiesService: ICapabilitiesService;
editorLoadedTimestamp?: number;
}
export interface ICellMagicMapper {
/**
* Tries to find a language mapping for an identified cell magic
* @param magic a string defining magic. For example for %%sql the magic text is sql
* @param kernelId the name of the current kernel to use when looking up magics
*/
toLanguageMagic(magic: string, kernelId: string): ILanguageMagic | undefined;
}
export interface INotebookContentsEditable {
cells: nb.ICellContents[];
metadata: nb.INotebookMetadata;
nbformat: number;
nbformat_minor: number;
}

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
export class NotebookContexts {
public static get DefaultContext(): ConnectionProfile {
return <any>{
providerName: mssqlProviderName,
id: '-1',
serverName: localize('selectConnection', "Select Connection")
};
}
public static get LocalContext(): ConnectionProfile {
return <any>{
providerName: mssqlProviderName,
id: '-1',
serverName: localize('localhost', "localhost")
};
}
/**
* Get the applicable context for a given kernel
* @param context current connection profile
* @param connProviderIds array of connection provider ids applicable for a kernel
*/
public static getContextForKernel(context: ConnectionProfile, connProviderIds: string[]): ConnectionProfile {
// If no connection provider ids exist for a given kernel, the attach to should show localhost
if (connProviderIds.length === 0) {
return NotebookContexts.LocalContext;
}
if (context && context.providerName && connProviderIds.filter(p => p === context.providerName).length > 0) {
return context;
}
return NotebookContexts.DefaultContext;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import { nb, ServerInfo } from 'azdata';
import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { URI } from 'vs/base/common/uri';
import { startsWith } from 'vs/base/common/strings';
import { assign } from 'vs/base/common/objects';
export const clusterEndpointsProperty = 'clusterEndpoints';
export const hadoopEndpointNameGateway = 'gateway';
/**
* Test whether an output is from a stream.
*/
export function isStream(output: nb.ICellOutput): output is nb.IStreamResult {
return output.output_type === 'stream';
}
export function getProvidersForFileName(fileName: string, notebookService: INotebookService): string[] {
let fileExt = path.extname(fileName);
let providers: string[];
// First try to get provider for actual file type
if (fileExt && startsWith(fileExt, '.')) {
fileExt = fileExt.slice(1, fileExt.length);
providers = notebookService.getProvidersForFileType(fileExt);
}
// Fallback to provider for default file type (assume this is a global handler)
if (!providers) {
providers = notebookService.getProvidersForFileType(DEFAULT_NOTEBOOK_FILETYPE);
}
// Finally if all else fails, use the built-in handler
if (!providers) {
providers = [DEFAULT_NOTEBOOK_PROVIDER];
}
return providers;
}
export function getStandardKernelsForProvider(providerId: string, notebookService: INotebookService): IStandardKernelWithProvider[] {
if (!providerId || !notebookService) {
return [];
}
let standardKernels = notebookService.getStandardKernelsForProvider(providerId);
standardKernels.forEach(kernel => {
assign(<IStandardKernelWithProvider>kernel, {
name: kernel.name,
connectionProviderIds: kernel.connectionProviderIds,
notebookProvider: providerId
});
});
return <IStandardKernelWithProvider[]>(standardKernels);
}
export interface IStandardKernelWithProvider {
readonly name: string;
readonly displayName: string;
readonly connectionProviderIds: string[];
readonly notebookProvider: string;
}
export interface IEndpoint {
serviceName: string;
description: string;
endpoint: string;
protocol: string;
}
export async function asyncForEach(array: any[], callback: Function): Promise<any> {
if (array && callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
}
export function getClusterEndpoints(serverInfo: ServerInfo): IEndpoint[] | undefined {
let endpoints: RawEndpoint[] = serverInfo.options[clusterEndpointsProperty];
if (!endpoints || endpoints.length === 0) { return []; }
return endpoints.map(e => {
// If endpoint is missing, we're on CTP bits. All endpoints from the CTP serverInfo should be treated as HTTPS
let endpoint = e.endpoint ? e.endpoint : `https://${e.ipAddress}:${e.port}`;
let updatedEndpoint: IEndpoint = {
serviceName: e.serviceName,
description: e.description,
endpoint: endpoint,
protocol: e.protocol
};
return updatedEndpoint;
});
}
export type HostAndIp = { host: string, port: string };
export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
let authority = URI.parse(endpoint).authority;
let hostAndPortRegex = /^(.*)([,:](\d+))/g;
let match = hostAndPortRegex.exec(authority);
if (match) {
return {
host: match[1],
port: match[3]
};
}
return {
host: authority,
port: undefined
};
}
export interface RawEndpoint {
serviceName: string;
description?: string;
endpoint?: string;
protocol?: string;
ipAddress?: string;
port?: number;
}
export interface IEndpoint {
serviceName: string;
description: string;
endpoint: string;
protocol: string;
}