diff --git a/extensions/mssql/config.json b/extensions/mssql/config.json
index ced60839a1..66ca52d736 100644
--- a/extensions/mssql/config.json
+++ b/extensions/mssql/config.json
@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
- "version": "3.0.0-release.8",
+ "version": "3.0.0-release.11",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp3.1.zip",
"Windows_64": "win-x64-netcoreapp3.1.zip",
diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json
index d54920288e..a509b6b62e 100644
--- a/extensions/mssql/package.json
+++ b/extensions/mssql/package.json
@@ -21,6 +21,18 @@
},
"contributes": {
"commands": [
+ {
+ "command": "mssql.exportSqlAsNotebook",
+ "title": "%mssql.exportSqlAsNotebook%"
+ },
+ {
+ "command": "mssql.exportNotebookToSql",
+ "title": "%mssql.exportNotebookToSql%",
+ "icon": {
+ "dark": "resources/dark/export_blue_dark.svg",
+ "light": "resources/light/export_blue_light.svg"
+ }
+ },
{
"command": "mssqlCluster.uploadFiles",
"title": "%mssqlCluster.uploadFiles%"
@@ -355,6 +367,14 @@
},
"menus": {
"commandPalette": [
+ {
+ "command": "mssql.exportSqlAsNotebook",
+ "when": "false"
+ },
+ {
+ "command": "mssql.exportNotebookToSql",
+ "when": "false"
+ },
{
"command": "mssqlCluster.uploadFiles",
"when": "false"
@@ -450,6 +470,12 @@
"when": "nodeType == mssqlCluster:file && nodeSubType =~/:spark:/",
"group": "1mssqlCluster@6"
}
+ ],
+ "notebook/toolbar": [
+ {
+ "command": "mssql.exportNotebookToSql",
+ "when": "providerId == sql"
+ }
]
},
"dashboard": {
diff --git a/extensions/mssql/package.nls.json b/extensions/mssql/package.nls.json
index 93ef9ae583..e0127e10a5 100644
--- a/extensions/mssql/package.nls.json
+++ b/extensions/mssql/package.nls.json
@@ -39,6 +39,8 @@
"mssql.disabled": "Disabled",
"mssql.enabled": "Enabled",
+ "mssql.exportNotebookToSql": "Export Notebook as SQL",
+ "mssql.exportSqlAsNotebook": "Export SQL as Notebook",
"mssql.configuration.title": "MSSQL configuration",
"mssql.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'",
"mssql.query.maxXmlCharsToStore": "Number of XML characters to store after running a query",
diff --git a/extensions/mssql/resources/dark/export_blue_dark.svg b/extensions/mssql/resources/dark/export_blue_dark.svg
new file mode 100644
index 0000000000..5f9f477494
--- /dev/null
+++ b/extensions/mssql/resources/dark/export_blue_dark.svg
@@ -0,0 +1,15 @@
+
diff --git a/extensions/mssql/resources/light/export_blue_light.svg b/extensions/mssql/resources/light/export_blue_light.svg
new file mode 100644
index 0000000000..09c9df4d2b
--- /dev/null
+++ b/extensions/mssql/resources/light/export_blue_light.svg
@@ -0,0 +1,15 @@
+
diff --git a/extensions/mssql/src/constants.ts b/extensions/mssql/src/constants.ts
index 4a4ac34575..c43f1ea4f7 100644
--- a/extensions/mssql/src/constants.ts
+++ b/extensions/mssql/src/constants.ts
@@ -31,6 +31,8 @@ export const hdfsRootPath = '/';
export const clusterEndpointsProperty = 'clusterEndpoints';
export const isBigDataClusterProperty = 'isBigDataCluster';
+export const ViewType = 'view';
+
// SERVICE NAMES //////////////////////////////////////////////////////////
export const ObjectExplorerService = 'objectexplorer';
export const CmsService = 'cmsService';
@@ -38,8 +40,8 @@ export const DacFxService = 'dacfxService';
export const SchemaCompareService = 'schemaCompareService';
export const LanguageExtensionService = 'languageExtensionService';
export const objectExplorerPrefix: string = 'objectexplorer://';
-export const ViewType = 'view';
export const SqlAssessmentService = 'sqlAssessmentService';
+export const NotebookConvertService = 'notebookConvertService';
export enum BuiltInCommands {
SetContext = 'setContext'
diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts
index 268925bbfa..43460dcaf9 100644
--- a/extensions/mssql/src/contracts.ts
+++ b/extensions/mssql/src/contracts.ts
@@ -747,3 +747,33 @@ export class CompletionExtensionParams {
export namespace CompletionExtLoadRequest {
export const type = new RequestType('completion/extLoad');
}
+
+// ------------------------------- < Load Completion Extension Request > ------------------------------------
+
+/// ------------------------------- -----------------------------
+
+export interface ConvertNotebookToSqlParams {
+ content: string;
+}
+
+export namespace ConvertNotebookToSqlRequest {
+ export const type = new RequestType('notebookconvert/convertnotebooktosql');
+}
+
+export interface ConvertNotebookToSqlResult extends azdata.ResultStatus {
+ content: string;
+}
+
+export interface ConvertSqlToNotebookParams {
+ clientUri: string;
+}
+
+export namespace ConvertSqlToNotebookRequest {
+ export const type = new RequestType('notebookconvert/convertsqltonotebook');
+}
+
+export interface ConvertSqlToNotebookResult extends azdata.ResultStatus {
+ content: string;
+}
+
+// ------------------------------- -----------------------------
diff --git a/extensions/mssql/src/main.ts b/extensions/mssql/src/main.ts
index 5d28005e2a..506d0c9ab9 100644
--- a/extensions/mssql/src/main.ts
+++ b/extensions/mssql/src/main.ts
@@ -30,6 +30,7 @@ import { SqlToolsServer } from './sqlToolsServer';
import { promises as fs } from 'fs';
import { IconPathHelper } from './iconHelper';
import * as nls from 'vscode-nls';
+import { INotebookConvertService } from './notebookConvert/notebookConvertService';
const localize = nls.loadMessageBundle();
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', "This sample code loads the file into a data frame and shows the first 10 results.");
@@ -79,6 +80,24 @@ export async function activate(context: vscode.ExtensionContext): Promise {
+ const result = await appContext.getService(Constants.NotebookConvertService).convertSqlToNotebook(uri.toString());
+ const title = findNextUntitledEditorName();
+ const untitledUri = vscode.Uri.parse(`untitled:${title}`);
+ await azdata.nb.showNotebookDocument(untitledUri, { initialContent: result.content });
+ });
+
+ vscode.commands.registerCommand('mssql.exportNotebookToSql', async (uri: vscode.Uri) => {
+ // SqlToolsService doesn't currently store anything about Notebook documents so we have to pass the raw JSON to it directly
+ // We use vscode.workspace.textDocuments here because the azdata.nb.notebookDocuments don't actually contain their contents
+ // (they're left out for perf purposes)
+ const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString());
+ const result = await appContext.getService(Constants.NotebookConvertService).convertNotebookToSql(doc.getText());
+
+ const sqlDoc = await vscode.workspace.openTextDocument({ language: 'sql', content: result.content });
+ await vscode.commands.executeCommand('vscode.open', sqlDoc.uri);
+ });
+
return createMssqlApi(appContext);
}
diff --git a/extensions/mssql/src/notebookConvert/notebookConvertService.ts b/extensions/mssql/src/notebookConvert/notebookConvertService.ts
new file mode 100644
index 0000000000..1b2e13a09c
--- /dev/null
+++ b/extensions/mssql/src/notebookConvert/notebookConvertService.ts
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { AppContext } from '../appContext';
+import { SqlOpsDataClient, ISqlOpsFeature } from 'dataprotocol-client';
+import { ClientCapabilities } from 'vscode-languageclient';
+import * as constants from '../constants';
+import * as contracts from '../contracts';
+
+export interface INotebookConvertService {
+ convertNotebookToSql(content: string): Promise;
+ convertSqlToNotebook(content: string): Promise;
+}
+
+export class NotebookConvertService implements INotebookConvertService {
+ public static asFeature(context: AppContext): ISqlOpsFeature {
+ return class extends NotebookConvertService {
+ constructor(client: SqlOpsDataClient) {
+ super(context, client);
+ }
+
+ fillClientCapabilities(capabilities: ClientCapabilities): void {
+ }
+
+ initialize(): void {
+ }
+ };
+ }
+
+ private constructor(context: AppContext, protected readonly client: SqlOpsDataClient) {
+ context.registerService(constants.NotebookConvertService, this);
+ }
+
+ async convertNotebookToSql(content: string): Promise {
+ let params: contracts.ConvertNotebookToSqlParams = { content: content };
+ try {
+ return this.client.sendRequest(contracts.ConvertNotebookToSqlRequest.type, params);
+ }
+ catch (e) {
+ this.client.logFailedRequest(contracts.ConvertNotebookToSqlRequest.type, e);
+ }
+
+ return undefined;
+ }
+ async convertSqlToNotebook(content: string): Promise {
+ let params: contracts.ConvertSqlToNotebookParams = { clientUri: content };
+ try {
+ return this.client.sendRequest(contracts.ConvertSqlToNotebookRequest.type, params);
+ }
+ catch (e) {
+ this.client.logFailedRequest(contracts.ConvertSqlToNotebookRequest.type, e);
+ }
+
+ return undefined;
+ }
+}
diff --git a/extensions/mssql/src/sqlToolsServer.ts b/extensions/mssql/src/sqlToolsServer.ts
index cb03a7cb54..0d0979ccda 100644
--- a/extensions/mssql/src/sqlToolsServer.ts
+++ b/extensions/mssql/src/sqlToolsServer.ts
@@ -23,6 +23,7 @@ import { promises as fs } from 'fs';
import * as nls from 'vscode-nls';
import { LanguageExtensionService } from './languageExtension/languageExtensionService';
import { SqlAssessmentService } from './sqlAssessment/sqlAssessmentService';
+import { NotebookConvertService } from './notebookConvert/notebookConvertService';
const localize = nls.loadMessageBundle();
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
@@ -160,7 +161,8 @@ function getClientOptions(context: AppContext): ClientOptions {
LanguageExtensionService.asFeature(context),
DacFxService.asFeature(context),
CmsService.asFeature(context),
- SqlAssessmentService.asFeature(context)
+ SqlAssessmentService.asFeature(context),
+ NotebookConvertService.asFeature(context)
],
outputChannel: new CustomOutputChannel()
};
diff --git a/src/sql/base/browser/ui/taskbar/media/icons.css b/src/sql/base/browser/ui/taskbar/media/icons.css
index deb3395318..6f736a2e05 100644
--- a/src/sql/base/browser/ui/taskbar/media/icons.css
+++ b/src/sql/base/browser/ui/taskbar/media/icons.css
@@ -118,3 +118,9 @@
background-image: url('disable_sqlcmd_inverse.svg');
background-repeat: no-repeat;
}
+
+.carbon-taskbar .codicon.export {
+ background-origin: initial;
+ background-position: left;
+ background-size: 11px
+}
diff --git a/src/sql/media/icons/common-icons.css b/src/sql/media/icons/common-icons.css
index 4175f3604b..a22bdbbd17 100644
--- a/src/sql/media/icons/common-icons.css
+++ b/src/sql/media/icons/common-icons.css
@@ -287,6 +287,15 @@
background-image: url("stop_inverse.svg");
}
+.hc-black .codicon.export,
+.vs-dark .codicon.export {
+ background: url("export_inverse.svg") center center no-repeat;
+}
+
+.vs .codicon.export {
+ background: url("export.svg") center center no-repeat;
+}
+
/* Notebook cells */
.codicon.toolbarIconRunInactive {
background-image: url("execute_cell_grey.svg");
diff --git a/src/sql/media/icons/export.svg b/src/sql/media/icons/export.svg
new file mode 100644
index 0000000000..719d989fcd
--- /dev/null
+++ b/src/sql/media/icons/export.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/sql/media/icons/export_inverse.svg b/src/sql/media/icons/export_inverse.svg
new file mode 100644
index 0000000000..1dee89afda
--- /dev/null
+++ b/src/sql/media/icons/export_inverse.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts
index a70a06f86f..0bb919cf09 100644
--- a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts
@@ -553,7 +553,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
// This is similar behavior that exists in MenuItemActionItem
if (action instanceof MenuItemAction) {
- if (action.item.id.includes('jupyter.cmd') && this.previewFeaturesEnabled) {
+ if ((action.item.id.includes('jupyter.cmd') && this.previewFeaturesEnabled) || action.item.id.includes('mssql')) {
action.tooltip = action.label;
action.label = '';
}
diff --git a/src/sql/workbench/contrib/query/browser/queryActions.ts b/src/sql/workbench/contrib/query/browser/queryActions.ts
index 85e1f8e897..3a13a76fb4 100644
--- a/src/sql/workbench/contrib/query/browser/queryActions.ts
+++ b/src/sql/workbench/contrib/query/browser/queryActions.ts
@@ -36,7 +36,7 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { getCurrentGlobalConnection } from 'sql/workbench/browser/taskUtilities';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { CommandsRegistry } from 'vs/platform/commands/common/commands';
+import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { OEAction } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions';
import { TreeViewItemHandleArg } from 'sql/workbench/common/views';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
@@ -813,3 +813,27 @@ export class ListDatabasesActionItem extends Disposable implements IActionViewIt
}
}
+
+/**
+ * Action class that sends the request to convert the contents of the sql editor
+ * into a Notebook document
+ */
+export class ExportAsNotebookAction extends QueryTaskbarAction {
+
+ public static IconClass = 'export';
+ public static ID = 'exportAsNotebookAction';
+
+ constructor(
+ editor: QueryEditor,
+ @IConnectionManagementService connectionManagementService: IConnectionManagementService,
+ @ICommandService private _commandService: ICommandService
+ ) {
+ super(connectionManagementService, editor, ConnectDatabaseAction.ID, ExportAsNotebookAction.IconClass);
+
+ this.label = nls.localize('queryEditor.exportSqlAsNotebook', "Export as Notebook");
+ }
+
+ public async run(): Promise {
+ this._commandService.executeCommand('mssql.exportSqlAsNotebook', this.editor.input.uri);
+ }
+}
diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts
index 5e2177b486..b9b12cc487 100644
--- a/src/sql/workbench/contrib/query/browser/queryEditor.ts
+++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts
@@ -86,6 +86,7 @@ export class QueryEditor extends BaseEditor {
private _actualQueryPlanAction: actions.ActualQueryPlanAction;
private _listDatabasesActionItem: actions.ListDatabasesActionItem;
private _toggleSqlcmdMode: actions.ToggleSqlCmdModeAction;
+ private _exportAsNotebookAction: actions.ExportAsNotebookAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@@ -183,6 +184,7 @@ export class QueryEditor extends BaseEditor {
this._estimatedQueryPlanAction = this.instantiationService.createInstance(actions.EstimatedQueryPlanAction, this);
this._actualQueryPlanAction = this.instantiationService.createInstance(actions.ActualQueryPlanAction, this);
this._toggleSqlcmdMode = this.instantiationService.createInstance(actions.ToggleSqlCmdModeAction, this, false);
+ this._exportAsNotebookAction = this.instantiationService.createInstance(actions.ExportAsNotebookAction, this);
this.setTaskbarContent();
@@ -266,13 +268,14 @@ export class QueryEditor extends BaseEditor {
{ action: this._listDatabasesAction },
{ element: separator },
{ action: this._estimatedQueryPlanAction },
- { action: this._toggleSqlcmdMode }
+ { action: this._toggleSqlcmdMode },
+ { action: this._exportAsNotebookAction }
];
// Remove the estimated query plan action if preview features are not enabled
let previewFeaturesEnabled = this.configurationService.getValue('workbench')['enablePreviewFeatures'];
if (!previewFeaturesEnabled) {
- content = content.slice(0, -2);
+ content.splice(7, 1);
}
this.taskbar.setContent(content);