diff --git a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts index a64e0577a3..4f7e5e4569 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts @@ -32,6 +32,7 @@ import { URI } from 'vs/base/common/uri'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { KernelsLanguage } from 'sql/workbench/services/notebook/common/notebookConstants'; const msgLoading = localize('loading', "Loading kernels..."); export const msgChanging = localize('changing', "Changing kernel..."); @@ -45,6 +46,7 @@ const msgLocalHost = localize('localhost', "localhost"); export const noKernel: string = localize('noKernel', "No Kernel"); const baseIconClass = 'codicon'; const maskedIconClass = 'masked-icon'; +export const kernelNotSupported: string = localize('kernelNotSupported', "This notebook cannot run with parameters as the kernel is not supported. Please use the supported kernels and format. [Learn more](https://docs.microsoft.com/sql/azure-data-studio/notebooks/notebooks-parameterization)."); export const noParameterCell: string = localize('noParametersCell', "This notebook cannot run with parameters until a parameter cell is added. [Learn more](https://docs.microsoft.com/sql/azure-data-studio/notebooks/notebooks-parameterization)."); export const noParametersInCell: string = localize('noParametersInCell', "This notebook cannot run with parameters until there are parameters added to the parameter cell. [Learn more](https://docs.microsoft.com/sql/azure-data-studio/notebooks/notebooks-parameterization)."); @@ -304,6 +306,16 @@ export class RunParametersAction extends TooltipFromLabelAction { */ public async run(context: URI): Promise { const editor = this._notebookService.findNotebookEditor(context); + // Only run action for kernels that are supported (Python, PySpark, PowerShell) + let supportedKernels: string[] = [KernelsLanguage.Python, KernelsLanguage.PowerShell]; + if (!supportedKernels.includes(editor.model.languageInfo.name)) { + // If the kernel is not supported indicate to user to use supported kernels + this.notificationService.notify({ + severity: Severity.Info, + message: kernelNotSupported, + }); + return; + } // Set defaultParameters to the parameter values in parameter cell let defaultParameters = new Map(); for (let cell of editor?.cells) { diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts index be4d140b12..bffe740f12 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as azdata from 'azdata'; import * as sinon from 'sinon'; import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; -import { AddCellAction, ClearAllOutputsAction, CollapseCellsAction, KernelsDropdown, msgChanging, NewNotebookAction, noKernelName, noParameterCell, noParametersInCell, RunAllCellsAction, RunParametersAction, TrustedAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; +import { AddCellAction, ClearAllOutputsAction, CollapseCellsAction, kernelNotSupported, KernelsDropdown, msgChanging, NewNotebookAction, noKernelName, noParameterCell, noParametersInCell, RunAllCellsAction, RunParametersAction, TrustedAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; import { ClientSessionStub, ContextViewProviderStub, NotebookComponentStub, NotebookModelStub, NotebookServiceStub } from 'sql/workbench/contrib/notebook/test/stubs'; import { NotebookEditorStub } from 'sql/workbench/contrib/notebook/test/testCommon'; import { ICellModel, INotebookModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; @@ -251,7 +251,7 @@ suite('Notebook Actions', function (): void { assert.strictEqual(actualCmdId, NewNotebookAction.INTERNAL_NEW_NOTEBOOK_CMD_ID); }); - test('Run with Parameters Action', async function (): Promise { + test('Should Run with Parameters Action', async function (): Promise { const testContents: azdata.nb.INotebookContents = { cells: [{ cell_type: CellTypes.Code, @@ -273,8 +273,10 @@ suite('Notebook Actions', function (): void { let mockNotification = TypeMoq.Mock.ofType(TestNotificationService); mockNotification.setup(n => n.notify(TypeMoq.It.isAny())); let quickInputService = new MockQuickInputService; - let mockNotebookModel = new NotebookModelStub(undefined, undefined, testContents); - + let testLanguageInfo: azdata.nb.ILanguageInfo = { + name: 'python', + }; + let mockNotebookModel = new NotebookModelStub(testLanguageInfo, undefined, testContents); let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object); const testCells = [{ @@ -293,7 +295,7 @@ suite('Notebook Actions', function (): void { assert.call(mockNotebookService.object.openNotebook(testUri, testShowOptions), 'Should Open Parameterized Notebook'); }); - test('Run with Parameters Action with no parameter cell in notebook', async function (): Promise { + test('Should inform user to add a parameter cell if Run with Parameters Action has no parameter cell', async function (): Promise { const testContents: azdata.nb.INotebookContents = { cells: [{ cell_type: CellTypes.Code, @@ -320,8 +322,10 @@ suite('Notebook Actions', function (): void { return undefined; }); let quickInputService = new MockQuickInputService; - let mockNotebookModel = new NotebookModelStub(undefined, undefined, testContents); - + let testLanguageInfo: azdata.nb.ILanguageInfo = { + name: 'python', + }; + let mockNotebookModel = new NotebookModelStub(testLanguageInfo, undefined, testContents); let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object); mockNotebookEditor.setup(x => x.model).returns(() => mockNotebookModel); @@ -332,7 +336,7 @@ suite('Notebook Actions', function (): void { assert.strictEqual(actualMsg, expectedMsg); }); - test('Run with Parameters Action with empty string parameter cell in notebook', async function (): Promise { + test('Should inform user to add parameters if Run with Parameters Action contains empty string parameter cell', async function (): Promise { const testContents: azdata.nb.INotebookContents = { cells: [{ cell_type: CellTypes.Code, @@ -359,8 +363,10 @@ suite('Notebook Actions', function (): void { return undefined; }); let quickInputService = new MockQuickInputService; - let mockNotebookModel = new NotebookModelStub(undefined, undefined, testContents); - + let testLanguageInfo: azdata.nb.ILanguageInfo = { + name: 'python', + }; + let mockNotebookModel = new NotebookModelStub(testLanguageInfo, undefined, testContents); let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object); const testCells = [{ isParameter: true, @@ -375,7 +381,7 @@ suite('Notebook Actions', function (): void { assert.strictEqual(actualMsg, expectedMsg); }); - test('Run with Parameters Action with empty array string parameter cell in notebook', async function (): Promise { + test('Should inform user to add parameters if Run with Parameters Action contains empty array string parameter cell', async function (): Promise { const testContents: azdata.nb.INotebookContents = { cells: [{ cell_type: CellTypes.Code, @@ -402,8 +408,10 @@ suite('Notebook Actions', function (): void { return undefined; }); let quickInputService = new MockQuickInputService; - let mockNotebookModel = new NotebookModelStub(undefined, undefined, testContents); - + let testLanguageInfo: azdata.nb.ILanguageInfo = { + name: 'python', + }; + let mockNotebookModel = new NotebookModelStub(testLanguageInfo, undefined, testContents); let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object); const testCells = [{ @@ -419,7 +427,7 @@ suite('Notebook Actions', function (): void { assert.strictEqual(actualMsg, expectedMsg); }); - test('Run with Parameters Action with empty parameter cell in notebook', async function (): Promise { + test('Should inform user to add parameters if Run with Parameters Action contains empty parameter cell', async function (): Promise { const testContents: azdata.nb.INotebookContents = { cells: [{ cell_type: CellTypes.Code, @@ -446,7 +454,10 @@ suite('Notebook Actions', function (): void { return undefined; }); let quickInputService = new MockQuickInputService; - let mockNotebookModel = new NotebookModelStub(undefined, undefined, testContents); + let testLanguageInfo: azdata.nb.ILanguageInfo = { + name: 'python', + }; + let mockNotebookModel = new NotebookModelStub(testLanguageInfo, undefined, testContents); let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object); @@ -463,6 +474,51 @@ suite('Notebook Actions', function (): void { assert.strictEqual(actualMsg, expectedMsg); }); + test('Should inform user kernel is not supported if Run with Parameters Action is run with unsupported kernels', async function (): Promise { + // Kernels that are supported (Python, PySpark, PowerShell) + + const testContents: azdata.nb.INotebookContents = { + cells: [{ + cell_type: CellTypes.Code, + source: [], + metadata: { language: 'sql' }, + execution_count: 1 + }], + metadata: { + kernelspec: { + name: 'sql', + language: 'sql', + display_name: 'SQL' + } + }, + nbformat: 4, + nbformat_minor: 5 + }; + let expectedMsg: string = kernelNotSupported; + + let actualMsg: string; + let mockNotification = TypeMoq.Mock.ofType(TestNotificationService); + mockNotification.setup(n => n.notify(TypeMoq.It.isAny())).returns(notification => { + actualMsg = notification.message; + return undefined; + }); + + let quickInputService = new MockQuickInputService; + let testLanguageInfo: azdata.nb.ILanguageInfo = { + name: 'sql', + }; + let mockNotebookModel = new NotebookModelStub(testLanguageInfo, undefined, testContents); + + let action = new RunParametersAction('TestId', true, testUri, quickInputService, mockNotebookService.object, mockNotification.object); + + mockNotebookEditor.setup(x => x.model).returns(() => mockNotebookModel); + + // Run Parameters Action + await action.run(testUri); + + assert.strictEqual(actualMsg, expectedMsg); + }); + suite('Kernels dropdown', async () => { let kernelsDropdown: KernelsDropdown; let contextViewProvider: ContextViewProviderStub; diff --git a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts index 5604201142..ad16ad6b6f 100644 --- a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts +++ b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts @@ -11,7 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IClientSession, INotebookModel, INotebookModelOptions, ICellModel, NotebookContentChange, MoveDirection, ViewMode } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { NotebookChangeType, CellType, CellTypes } from 'sql/workbench/services/notebook/common/contracts'; -import { nbversion } from 'sql/workbench/services/notebook/common/notebookConstants'; +import { KernelsLanguage, nbversion } from 'sql/workbench/services/notebook/common/notebookConstants'; import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; @@ -882,12 +882,12 @@ export class NotebookModel extends Disposable implements INotebookModel { language = language.replace(mimeTypePrefix, ''); } else if (language.toLowerCase() === 'ipython') { // Special case ipython because in many cases this is defined as the code mirror mode for python notebooks - language = 'python'; + language = KernelsLanguage.Python; } else if (language.toLowerCase() === 'c#') { - language = 'cs'; + language = KernelsLanguage.CSharp; } } else { - language = 'python'; + language = KernelsLanguage.Python; } this._language = language.toLowerCase(); diff --git a/src/sql/workbench/services/notebook/common/notebookConstants.ts b/src/sql/workbench/services/notebook/common/notebookConstants.ts index f9b5c7c69d..9042ad0a3c 100644 --- a/src/sql/workbench/services/notebook/common/notebookConstants.ts +++ b/src/sql/workbench/services/notebook/common/notebookConstants.ts @@ -13,4 +13,14 @@ export namespace nbversion { * The minor version of the notebook format. */ export const MINOR_VERSION: number = 2; -} \ No newline at end of file +} + +export enum KernelsLanguage { + SQL = 'sql', + Python = 'python', + PySpark = 'python', + SparkScala = 'scala', + SparkR = 'sparkr', + PowerShell = 'powershell', + CSharp = 'cs' +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 1361cab9bf..35d93e62e5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -36,6 +36,7 @@ export enum CellOutputKind { Rich = 3 } + export const NOTEBOOK_DISPLAY_ORDER = [ 'application/json', 'application/javascript',