mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Notebooks: Adding Change Kernel API, 3 Integration Tests (#5287)
* Notebook change kernel * Fix notifying of k change too many times add tests * Fix broken unit test * Deal with comment
This commit is contained in:
@@ -10,7 +10,7 @@ import * as assert from 'assert';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { context } from './testContext';
|
import { context } from './testContext';
|
||||||
import { sqlNotebookContent, writeNotebookToFile, sqlKernelMetadata, getFileName, pySparkNotebookContent, pySpark3KernelMetadata, pythonKernelMetadata, sqlNotebookMultipleCellsContent, notebookContentForCellLanguageTest } from './notebook.util';
|
import { sqlNotebookContent, writeNotebookToFile, sqlKernelMetadata, getFileName, pySparkNotebookContent, pySpark3KernelMetadata, pythonKernelMetadata, sqlNotebookMultipleCellsContent, notebookContentForCellLanguageTest, sqlKernelSpec, pythonKernelSpec, pySpark3KernelSpec } from './notebook.util';
|
||||||
import { getBdcServer, getConfigValue, EnvironmentVariable_PYTHON_PATH } from './testConfig';
|
import { getBdcServer, getConfigValue, EnvironmentVariable_PYTHON_PATH } from './testConfig';
|
||||||
import { connectToServer } from './utils';
|
import { connectToServer } from './utils';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@@ -56,6 +56,18 @@ if (context.RunTest) {
|
|||||||
test('python language test', async function () {
|
test('python language test', async function () {
|
||||||
await (new NotebookTester()).pythonLanguageTest(this.test.title);
|
await (new NotebookTester()).pythonLanguageTest(this.test.title);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Change kernel different provider SQL to Python to SQL', async function () {
|
||||||
|
await (new NotebookTester()).sqlNbChangeKernelDifferentProviderTest(this.test.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Change kernel different provider Python to SQL to Python', async function () {
|
||||||
|
await (new NotebookTester()).pythonChangeKernelDifferentProviderTest(this.test.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Change kernel same provider Python to PySpark3 to Python', async function () {
|
||||||
|
await (new NotebookTester()).pythonChangeKernelSameProviderTest(this.test.title);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env['RUN_PYSPARK_TEST'] === '1') {
|
if (process.env['RUN_PYSPARK_TEST'] === '1') {
|
||||||
@@ -154,6 +166,48 @@ class NotebookTester {
|
|||||||
assert(actualOutput2[0] === '1', `Expected result: 1, Actual: '${actualOutput2[0]}'`);
|
assert(actualOutput2[0] === '1', `Expected result: 1, Actual: '${actualOutput2[0]}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sqlNbChangeKernelDifferentProviderTest(title: string): Promise<void> {
|
||||||
|
let notebook = await this.openNotebook(sqlNotebookContent, sqlKernelMetadata, title);
|
||||||
|
assert(notebook.document.providerId === 'sql', `Expected providerId to be sql, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(notebook.document.kernelSpec.name === 'SQL', `Expected first kernel name: SQL, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
|
||||||
|
let kernelChanged = await notebook.changeKernel(pythonKernelSpec);
|
||||||
|
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(kernelChanged && notebook.document.kernelSpec.name === 'python3', `Expected second kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
|
||||||
|
kernelChanged = await notebook.changeKernel(sqlKernelSpec);
|
||||||
|
assert(notebook.document.providerId === 'sql', `Expected providerId to be sql, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(kernelChanged && notebook.document.kernelSpec.name === 'SQL', `Expected third kernel name: SQL, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async pythonChangeKernelDifferentProviderTest(title: string): Promise<void> {
|
||||||
|
let notebook = await this.openNotebook(pySparkNotebookContent, pythonKernelMetadata, title);
|
||||||
|
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(notebook.document.kernelSpec.name === 'python3', `Expected first kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
|
||||||
|
let kernelChanged = await notebook.changeKernel(sqlKernelSpec);
|
||||||
|
assert(notebook.document.providerId === 'sql', `Expected providerId to be sql, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(kernelChanged && notebook.document.kernelSpec.name === 'SQL', `Expected second kernel name: SQL, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
|
||||||
|
kernelChanged = await notebook.changeKernel(pythonKernelSpec);
|
||||||
|
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(kernelChanged && notebook.document.kernelSpec.name === 'python3', `Expected third kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async pythonChangeKernelSameProviderTest(title: string): Promise<void> {
|
||||||
|
let notebook = await this.openNotebook(pySparkNotebookContent, pythonKernelMetadata, title);
|
||||||
|
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(notebook.document.kernelSpec.name === 'python3', `Expected first kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
|
||||||
|
let kernelChanged = await notebook.changeKernel(pySpark3KernelSpec);
|
||||||
|
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(kernelChanged && notebook.document.kernelSpec.name === 'pyspark3kernel', `Expected second kernel name: pyspark3kernel, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
|
||||||
|
kernelChanged = await notebook.changeKernel(pythonKernelSpec);
|
||||||
|
assert(notebook.document.providerId === 'jupyter', `Expected providerId to be jupyter, Actual: ${notebook.document.providerId}`);
|
||||||
|
assert(kernelChanged && notebook.document.kernelSpec.name === 'python3', `Expected third kernel name: python3, Actual: ${notebook.document.kernelSpec.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
async scalaLanguageTest(title: string): Promise<void> {
|
async scalaLanguageTest(title: string): Promise<void> {
|
||||||
let language = 'scala';
|
let language = 'scala';
|
||||||
await this.cellLanguageTest(notebookContentForCellLanguageTest, title + this.invocationCount++, language, {
|
await this.cellLanguageTest(notebookContentForCellLanguageTest, title + this.invocationCount++, language, {
|
||||||
|
|||||||
@@ -134,6 +134,11 @@ export const pySpark3KernelMetadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const pySpark3KernelSpec = {
|
||||||
|
name: 'pyspark3',
|
||||||
|
display_name: 'PySpark3'
|
||||||
|
};
|
||||||
|
|
||||||
export const sqlKernelMetadata = {
|
export const sqlKernelMetadata = {
|
||||||
'kernelspec': {
|
'kernelspec': {
|
||||||
'name': 'SQL',
|
'name': 'SQL',
|
||||||
@@ -141,6 +146,11 @@ export const sqlKernelMetadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sqlKernelSpec: azdata.nb.IKernelSpec = {
|
||||||
|
name: 'SQL',
|
||||||
|
display_name: 'SQL'
|
||||||
|
};
|
||||||
|
|
||||||
export const pythonKernelMetadata = {
|
export const pythonKernelMetadata = {
|
||||||
'kernelspec': {
|
'kernelspec': {
|
||||||
'name': 'python3',
|
'name': 'python3',
|
||||||
@@ -148,6 +158,11 @@ export const pythonKernelMetadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const pythonKernelSpec: azdata.nb.IKernelSpec = {
|
||||||
|
name: 'python3',
|
||||||
|
display_name: 'Python 3'
|
||||||
|
};
|
||||||
|
|
||||||
export function writeNotebookToFile(pythonNotebook: azdata.nb.INotebookContents, testName: string): vscode.Uri {
|
export function writeNotebookToFile(pythonNotebook: azdata.nb.INotebookContents, testName: string): vscode.Uri {
|
||||||
let fileName = getFileName(testName);
|
let fileName = getFileName(testName);
|
||||||
let notebookContentString = JSON.stringify(pythonNotebook);
|
let notebookContentString = JSON.stringify(pythonNotebook);
|
||||||
|
|||||||
5
src/sql/azdata.proposed.d.ts
vendored
5
src/sql/azdata.proposed.d.ts
vendored
@@ -4363,6 +4363,11 @@ declare module 'azdata' {
|
|||||||
* @return A promise that resolves with a value indicating if the outputs are cleared or not.
|
* @return A promise that resolves with a value indicating if the outputs are cleared or not.
|
||||||
*/
|
*/
|
||||||
clearAllOutputs(): Thenable<boolean>;
|
clearAllOutputs(): Thenable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the Notebook's kernel. Thenable will resolve only after kernel change is complete.
|
||||||
|
*/
|
||||||
|
changeKernel(kernel: IKernelSpec): Thenable<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotebookCell {
|
export interface NotebookCell {
|
||||||
|
|||||||
@@ -163,6 +163,10 @@ export class ExtHostNotebookEditor implements azdata.nb.NotebookEditor, IDisposa
|
|||||||
return this._proxy.$clearAllOutputs(this._id);
|
return this._proxy.$clearAllOutputs(this._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public changeKernel(kernel: azdata.nb.IKernelSpec): Thenable<boolean> {
|
||||||
|
return this._proxy.$changeKernel(this._id, kernel);
|
||||||
|
}
|
||||||
|
|
||||||
public edit(callback: (editBuilder: azdata.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
public edit(callback: (editBuilder: azdata.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
||||||
if (this._disposed) {
|
if (this._disposed) {
|
||||||
return Promise.reject(new Error('NotebookEditor#edit not possible on closed editors'));
|
return Promise.reject(new Error('NotebookEditor#edit not possible on closed editors'));
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ import { localize } from 'vs/nls';
|
|||||||
class MainThreadNotebookEditor extends Disposable {
|
class MainThreadNotebookEditor extends Disposable {
|
||||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||||
public readonly contentChanged: Event<NotebookContentChange> = this._contentChangedEmitter.event;
|
public readonly contentChanged: Event<NotebookContentChange> = this._contentChangedEmitter.event;
|
||||||
private _providerInfo: IProviderInfo;
|
private _providerId: string = '';
|
||||||
|
private _providers: string[] = [];
|
||||||
|
|
||||||
constructor(public readonly editor: INotebookEditor) {
|
constructor(public readonly editor: INotebookEditor) {
|
||||||
super();
|
super();
|
||||||
editor.modelReady.then(model => {
|
editor.modelReady.then(model => {
|
||||||
|
this._providerId = model.providerId;
|
||||||
|
|
||||||
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
|
this._register(model.contentChanged((e) => this._contentChangedEmitter.fire(e)));
|
||||||
this._register(model.kernelChanged((e) => {
|
this._register(model.kernelChanged((e) => {
|
||||||
let changeEvent: NotebookContentChange = {
|
let changeEvent: NotebookContentChange = {
|
||||||
@@ -48,9 +51,12 @@ class MainThreadNotebookEditor extends Disposable {
|
|||||||
};
|
};
|
||||||
this._contentChangedEmitter.fire(changeEvent);
|
this._contentChangedEmitter.fire(changeEvent);
|
||||||
}));
|
}));
|
||||||
|
this._register(model.onProviderIdChange((provider) => {
|
||||||
|
this._providerId = provider;
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
editor.notebookParams.providerInfo.then(info => {
|
editor.notebookParams.providerInfo.then(info => {
|
||||||
this._providerInfo = info;
|
this._providers = info.providers;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +73,11 @@ class MainThreadNotebookEditor extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get providerId(): string {
|
public get providerId(): string {
|
||||||
return this._providerInfo ? this._providerInfo.providerId : undefined;
|
return this._providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get providers(): string[] {
|
public get providers(): string[] {
|
||||||
return this._providerInfo ? this._providerInfo.providers : [];
|
return this._providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get cells(): ICellModel[] {
|
public get cells(): ICellModel[] {
|
||||||
@@ -306,7 +312,7 @@ class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
|
|||||||
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
|
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
|
||||||
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
|
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
|
||||||
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
|
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
|
||||||
private _modelToDisposeMap = new Map<string, IDisposable>();
|
private _modelToDisposeMap = new Map<string, IDisposable[]>();
|
||||||
constructor(
|
constructor(
|
||||||
extHostContext: IExtHostContext,
|
extHostContext: IExtHostContext,
|
||||||
@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
|
@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
|
||||||
@@ -385,6 +391,14 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
return editor.clearAllOutputs();
|
return editor.clearAllOutputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$changeKernel(id: string, kernel: azdata.nb.IKernelSpec): Promise<boolean> {
|
||||||
|
let editor = this.getEditor(id);
|
||||||
|
if (!editor) {
|
||||||
|
return Promise.reject(disposed(`TextEditor(${id})`));
|
||||||
|
}
|
||||||
|
return this.doChangeKernel(editor, kernel.display_name);
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
|
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
|
||||||
@@ -500,9 +514,11 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removedDocuments.forEach(removedDoc => {
|
removedDocuments.forEach(removedDoc => {
|
||||||
let listener = this._modelToDisposeMap.get(removedDoc.toString());
|
let listeners = this._modelToDisposeMap.get(removedDoc.toString());
|
||||||
if (listener) {
|
if (listeners && listeners.length) {
|
||||||
listener.dispose();
|
listeners.forEach(listener => {
|
||||||
|
listener.dispose();
|
||||||
|
});
|
||||||
this._modelToDisposeMap.delete(removedDoc.toString());
|
this._modelToDisposeMap.delete(removedDoc.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -514,9 +530,9 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
}
|
}
|
||||||
addedEditors.forEach(editor => {
|
addedEditors.forEach(editor => {
|
||||||
let modelUrl = editor.uri;
|
let modelUrl = editor.uri;
|
||||||
this._modelToDisposeMap.set(editor.uri.toString(), editor.contentChanged((e) => {
|
this._modelToDisposeMap.set(editor.uri.toString(), [editor.contentChanged((e) => {
|
||||||
this._proxy.$acceptModelChanged(modelUrl, this._toNotebookChangeData(e, editor));
|
this._proxy.$acceptModelChanged(modelUrl, this._toNotebookChangeData(e, editor));
|
||||||
}));
|
})]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,4 +607,15 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
}
|
}
|
||||||
return notebookCells;
|
return notebookCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async doChangeKernel(editor: MainThreadNotebookEditor, displayName: string): Promise<boolean> {
|
||||||
|
let listeners = this._modelToDisposeMap.get(editor.id);
|
||||||
|
editor.model.changeKernel(displayName);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
listeners.push(editor.model.kernelChanged((kernel) => {
|
||||||
|
resolve(true);
|
||||||
|
}));
|
||||||
|
this._modelToDisposeMap.set(editor.id, listeners);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -900,6 +900,7 @@ export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable
|
|||||||
$runCell(id: string, cellUri: UriComponents): Promise<boolean>;
|
$runCell(id: string, cellUri: UriComponents): Promise<boolean>;
|
||||||
$runAllCells(id: string): Promise<boolean>;
|
$runAllCells(id: string): Promise<boolean>;
|
||||||
$clearAllOutputs(id: string): Promise<boolean>;
|
$clearAllOutputs(id: string): Promise<boolean>;
|
||||||
|
$changeKernel(id: string, kernel: azdata.nb.IKernelInfo): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostExtensionManagementShape {
|
export interface ExtHostExtensionManagementShape {
|
||||||
|
|||||||
@@ -264,8 +264,11 @@ export class ClientSession implements IClientSession {
|
|||||||
private async updateCachedKernelSpec(): Promise<void> {
|
private async updateCachedKernelSpec(): Promise<void> {
|
||||||
this._cachedKernelSpec = undefined;
|
this._cachedKernelSpec = undefined;
|
||||||
let kernel = this.kernel;
|
let kernel = this.kernel;
|
||||||
if (kernel && kernel.isReady) {
|
if (kernel) {
|
||||||
this._cachedKernelSpec = await this.kernel.getSpec();
|
await kernel.ready;
|
||||||
|
if (kernel.isReady) {
|
||||||
|
this._cachedKernelSpec = await kernel.getSpec();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -332,6 +332,11 @@ export interface INotebookModel {
|
|||||||
*/
|
*/
|
||||||
readonly contentChanged: Event<NotebookContentChange>;
|
readonly contentChanged: Event<NotebookContentChange>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired on notebook provider change
|
||||||
|
*/
|
||||||
|
readonly onProviderIdChange: Event<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The trusted mode of the Notebook
|
* The trusted mode of the Notebook
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -457,10 +457,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._onClientSessionReady.fire(clientSession);
|
this._onClientSessionReady.fire(clientSession);
|
||||||
this._kernelChangedEmitter.fire({
|
|
||||||
oldValue: undefined,
|
|
||||||
newValue: clientSession.kernel
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ export class NotebookModelStub implements INotebookModel {
|
|||||||
get onValidConnectionSelected(): Event<boolean> {
|
get onValidConnectionSelected(): Event<boolean> {
|
||||||
throw new Error('method not implemented.');
|
throw new Error('method not implemented.');
|
||||||
}
|
}
|
||||||
|
get onProviderIdChange(): Event<string> {
|
||||||
|
throw new Error('method not impelemented.');
|
||||||
|
}
|
||||||
toJSON(): nb.INotebookContents {
|
toJSON(): nb.INotebookContents {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user