mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-16 17:22:29 -05:00
Support Notebook integration testing by adding APIs & fixing others (#3848)
- Added `runCell` API. Updated runCell button to listen to events on the model so it'll reflect run cell when called from other sources
- Plumbed through kernelspec info to the extension side so when changed, it's updated
- Fixed bug in ConnectionProfile where it didn't copy from options but instead overrode with empty wrapper functions
Here's the rough test code (it's in the sql-vnext extension and will be out in a separate PR)
```ts
it('Should connect to local notebook server with result 2', async function() {
this.timeout(60000);
let pythonNotebook = Object.assign({}, expectedNotebookContent, { metadata: { kernelspec: { name: "python3", display_name: "Python 3" }}});
let uri = writeNotebookToFile(pythonNotebook);
await ensureJupyterInstalled();
let notebook = await sqlops.nb.showNotebookDocument(uri);
should(notebook.document.cells).have.length(1);
let ran = await notebook.runCell(notebook.document.cells[0]);
should(ran).be.true('Notebook runCell failed');
let cellOutputs = notebook.document.cells[0].contents.outputs;
should(cellOutputs).have.length(1);
let result = (<sqlops.nb.IExecuteResult>cellOutputs[0]).data['text/plain'];
should(result).equal('2');
try {
// TODO support closing the editor. Right now this prompts and there's no override for this. Need to fix in core
// Close the editor using the recommended vscode API
//await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
}
catch (e) {}
});
it('Should connect to remote spark server with result 2', async function() {
this.timeout(240000);
let uri = writeNotebookToFile(expectedNotebookContent);
await ensureJupyterInstalled();
// Given a connection to a server exists
let connectionId = await connectToSparkIntegrationServer();
// When I open a Spark notebook and run the cell
let notebook = await sqlops.nb.showNotebookDocument(uri, {
connectionId: connectionId
});
should(notebook.document.cells).have.length(1);
let ran = await notebook.runCell(notebook.document.cells[0]);
should(ran).be.true('Notebook runCell failed');
// Then I expect to get the output result of 1+1, executed remotely against the Spark endpoint
let cellOutputs = notebook.document.cells[0].contents.outputs;
should(cellOutputs).have.length(4);
let sparkResult = (<sqlops.nb.IStreamResult>cellOutputs[3]).text;
should(sparkResult).equal('2');
try {
// TODO support closing the editor. Right now this prompts and there's no override for this. Need to fix in core
// Close the editor using the recommended vscode API
//await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
}
catch (e) {}
});
});
```
This commit is contained in:
@@ -164,7 +164,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
|
||||
private hookFutureDone(futureId: number, future: sqlops.nb.IFuture): void {
|
||||
future.done.then(success => {
|
||||
return this._proxy.$onFutureDone(futureId, { succeeded: true, rejectReason: undefined });
|
||||
return this._proxy.$onFutureDone(futureId, { succeeded: true, message: success, rejectReason: undefined });
|
||||
}, err => {
|
||||
let rejectReason: string;
|
||||
if (typeof err === 'string') {
|
||||
@@ -176,7 +176,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
else {
|
||||
rejectReason = err;
|
||||
}
|
||||
return this._proxy.$onFutureDone(futureId, { succeeded: false, rejectReason: rejectReason });
|
||||
return this._proxy.$onFutureDone(futureId, { succeeded: false, message: undefined, rejectReason: rejectReason });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { CellRange } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
export class ExtHostNotebookDocumentData implements IDisposable {
|
||||
private _document: sqlops.nb.NotebookDocument;
|
||||
private _isDisposed: boolean = false;
|
||||
private _kernelSpec: sqlops.nb.IKernelSpec;
|
||||
|
||||
constructor(private readonly _proxy: MainThreadNotebookDocumentsAndEditorsShape,
|
||||
private readonly _uri: URI,
|
||||
@@ -49,6 +50,7 @@ export class ExtHostNotebookDocumentData implements IDisposable {
|
||||
get isClosed() { return data._isDisposed; },
|
||||
get isDirty() { return data._isDirty; },
|
||||
get cells() { return data._cells; },
|
||||
get kernelSpec() { return data._kernelSpec; },
|
||||
save() { return data._save(); },
|
||||
validateCellRange(range) { return data._validateRange(range); },
|
||||
};
|
||||
@@ -69,6 +71,7 @@ export class ExtHostNotebookDocumentData implements IDisposable {
|
||||
this._isDirty = data.isDirty;
|
||||
this._cells = data.cells;
|
||||
this._providerId = data.providerId;
|
||||
this._kernelSpec = data.kernelSpec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,11 @@ export class ExtHostNotebookEditor implements sqlops.nb.NotebookEditor, IDisposa
|
||||
return this._id;
|
||||
}
|
||||
|
||||
edit(callback: (editBuilder: sqlops.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
||||
public runCell(cell: sqlops.nb.NotebookCell): Thenable<boolean> {
|
||||
return this._proxy.$runCell(this._id, cell.uri);
|
||||
}
|
||||
|
||||
public edit(callback: (editBuilder: sqlops.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
||||
if (this._disposed) {
|
||||
return TPromise.wrapError<boolean>(new Error('NotebookEditor#edit not possible on closed editors'));
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ class FutureWrapper implements FutureInternal {
|
||||
public onDone(done: INotebookFutureDone): void {
|
||||
this._inProgress = false;
|
||||
if (done.succeeded) {
|
||||
this._done.resolve();
|
||||
this._done.resolve(done.message);
|
||||
} else {
|
||||
this._done.reject(new Error(done.rejectReason));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
|
||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { disposed } from 'vs/base/common/errors';
|
||||
import { ICellModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
||||
|
||||
class MainThreadNotebookEditor extends Disposable {
|
||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||
@@ -38,6 +39,12 @@ class MainThreadNotebookEditor extends Disposable {
|
||||
super();
|
||||
editor.modelReady.then(model => {
|
||||
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
|
||||
this._register(model.kernelChanged((e) => {
|
||||
let changeEvent: NotebookContentChange = {
|
||||
changeType: NotebookChangeType.KernelChanged
|
||||
};
|
||||
this._contentChangedEmitter.fire(changeEvent);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,6 +72,10 @@ class MainThreadNotebookEditor extends Disposable {
|
||||
return this.editor.cells;
|
||||
}
|
||||
|
||||
public get model(): INotebookModel | null {
|
||||
return this.editor.model;
|
||||
}
|
||||
|
||||
public save(): Thenable<boolean> {
|
||||
return this.editor.save();
|
||||
}
|
||||
@@ -100,6 +111,14 @@ class MainThreadNotebookEditor extends Disposable {
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
public runCell(cell: ICellModel): Promise<boolean> {
|
||||
if (!this.editor) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return this.editor.runCell(cell);
|
||||
}
|
||||
}
|
||||
|
||||
function wait(timeMs: number): Promise<void> {
|
||||
@@ -266,7 +285,6 @@ class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadNotebookDocumentsAndEditors)
|
||||
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
|
||||
|
||||
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
|
||||
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
|
||||
private _modelToDisposeMap = new Map<string, IDisposable>();
|
||||
@@ -308,6 +326,22 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
||||
}
|
||||
return TPromise.as(editor.applyEdits(modelVersionId, edits, opts));
|
||||
}
|
||||
|
||||
$runCell(id: string, cellUri: UriComponents): TPromise<boolean, any> {
|
||||
// Requires an editor and the matching cell in that editor
|
||||
let editor = this.getEditor(id);
|
||||
if (!editor) {
|
||||
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
let uriString = URI.revive(cellUri).toString();
|
||||
let cell = editor.cells.find(c => c.cellUri.toString() === uriString);
|
||||
if (!cell) {
|
||||
return TPromise.wrapError<boolean>(disposed(`TextEditorCell(${uriString})`));
|
||||
}
|
||||
|
||||
return TPromise.wrap(editor.runCell(cell));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
|
||||
@@ -480,11 +514,17 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
||||
isDirty: e.isDirty,
|
||||
providerId: editor.providerId,
|
||||
providers: editor.providers,
|
||||
uri: editor.uri
|
||||
uri: editor.uri,
|
||||
kernelSpec: this.getKernelSpec(editor)
|
||||
};
|
||||
return changeData;
|
||||
}
|
||||
|
||||
private getKernelSpec(editor: MainThreadNotebookEditor): sqlops.nb.IKernelSpec {
|
||||
let spec = editor && editor.model && editor.model.clientSession ? editor.model.clientSession.cachedKernelSpec : undefined;
|
||||
return spec;
|
||||
}
|
||||
|
||||
private convertCellModelToNotebookCell(cells: ICellModel | ICellModel[]): sqlops.nb.NotebookCell[] {
|
||||
let notebookCells: sqlops.nb.NotebookCell[] = [];
|
||||
if (Array.isArray(cells)) {
|
||||
@@ -497,8 +537,8 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
||||
metadata: {
|
||||
language: cell.language
|
||||
},
|
||||
source: undefined
|
||||
|
||||
source: undefined,
|
||||
outputs: [...cell.outputs]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -830,6 +830,7 @@ export interface INotebookModelChangedData {
|
||||
providers: string[];
|
||||
isDirty: boolean;
|
||||
cells: sqlops.nb.NotebookCell[];
|
||||
kernelSpec: sqlops.nb.IKernelSpec;
|
||||
}
|
||||
|
||||
export interface INotebookEditorAddData {
|
||||
@@ -856,6 +857,7 @@ export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable
|
||||
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
|
||||
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise<string>;
|
||||
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): TPromise<boolean>;
|
||||
$runCell(id: string, cellUri: UriComponents): TPromise<boolean>;
|
||||
}
|
||||
|
||||
export interface ExtHostExtensionManagementShape {
|
||||
|
||||
Reference in New Issue
Block a user