Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb35dae1d1 | ||
|
|
cc0a144169 | ||
|
|
b973f9e0ec | ||
|
|
d62068025c | ||
|
|
b2952d2ddf | ||
|
|
a21244816d | ||
|
|
a9aeb57dc4 | ||
|
|
00537ed199 | ||
|
|
c2df3e0e0a | ||
|
|
e3afb1cffc | ||
|
|
b475311f85 | ||
|
|
45005d61e0 | ||
|
|
adfddeae27 | ||
|
|
a7429267bb | ||
|
|
ff415d6a03 | ||
|
|
b4dc35a4de | ||
|
|
29c7ccad39 | ||
|
|
1da3635d03 | ||
|
|
1f50015ed2 | ||
|
|
01892422cb | ||
|
|
afce60b06f | ||
|
|
a2b87f6158 | ||
|
|
76a2f92daf | ||
|
|
5b09d57196 | ||
|
|
95bf18f859 | ||
|
|
1fc648ff37 | ||
|
|
37ba956bad | ||
|
|
697f887539 | ||
|
|
7b42141958 | ||
|
|
158b00f9b4 | ||
|
|
43ac8dfd20 | ||
|
|
34d8d52e7a | ||
|
|
c738b26c04 | ||
|
|
6090e7173f | ||
|
|
7ebf746584 | ||
|
|
e9d04d75ac | ||
|
|
605160a1ba | ||
|
|
82b8750b63 | ||
|
|
61f7f19d12 |
@@ -114,7 +114,7 @@ const vscodeResources = [
|
||||
'out-build/sql/parts/jobManagement/common/media/*.svg',
|
||||
'out-build/sql/media/objectTypes/*.svg',
|
||||
'out-build/sql/media/icons/*.svg',
|
||||
'out-build/sql/parts/notebook/media/**/*.svg',
|
||||
'out-build/sql/workbench/parts/notebook/media/**/*.svg',
|
||||
'!**/test/**'
|
||||
];
|
||||
|
||||
|
||||
@@ -134,8 +134,10 @@ export class MarkdownEngine {
|
||||
|
||||
// {{SQL CARBON EDIT}} - Add renderText method
|
||||
public async renderText(document: vscode.Uri, text: string): Promise<string> {
|
||||
const engine = await this.getEngine(this.getConfig(document));
|
||||
return engine.render(text);
|
||||
const config = this.getConfig(document);
|
||||
const engine = await this.getEngine(config);
|
||||
this.currentDocument = document;
|
||||
return engine.render(text, config);
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
|
||||
@@ -109,6 +109,14 @@
|
||||
{
|
||||
"command": "mssqlCluster.livy.cmd.submitFileToSparkJob",
|
||||
"title": "%title.submitSparkJob%"
|
||||
},
|
||||
{
|
||||
"command": "mssql.searchServers",
|
||||
"title": "%title.searchServers%"
|
||||
},
|
||||
{
|
||||
"command": "mssql.clearSearchServerResult",
|
||||
"title": "%title.clearSearchServerResult%"
|
||||
}
|
||||
],
|
||||
"outputChannels": [
|
||||
|
||||
@@ -24,5 +24,8 @@
|
||||
"title.openYarnHistory": "View Yarn History",
|
||||
"title.tasks": "Tasks",
|
||||
"title.installPackages": "Install Packages",
|
||||
"title.configurePython": "Configure Python for Notebooks"
|
||||
"title.configurePython": "Configure Python for Notebooks",
|
||||
|
||||
"title.searchServers": "Search: Servers",
|
||||
"title.clearSearchServerResult": "Search: Clear Search Server Results"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.5.0-alpha.84",
|
||||
"version": "1.5.0-alpha.85",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||
|
||||
@@ -13,7 +13,8 @@ export const extensionConfigSectionName = 'mssql';
|
||||
|
||||
// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
|
||||
export const mssqlClusterProviderName = 'mssqlCluster';
|
||||
export const hadoopKnoxEndpointName = 'Knox';
|
||||
export const hadoopEndpointNameKnox = 'Knox';
|
||||
export const hadoopEndpointNameGateway = 'gateway';
|
||||
export const protocolVersion = '1.0';
|
||||
export const hostPropName = 'host';
|
||||
export const userPropName = 'user';
|
||||
|
||||
@@ -32,6 +32,7 @@ import { OpenSparkJobSubmissionDialogCommand, OpenSparkJobSubmissionDialogFromFi
|
||||
import { OpenSparkYarnHistoryTask } from './sparkFeature/historyTask';
|
||||
import { MssqlObjectExplorerNodeProvider, mssqlOutputChannel } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
|
||||
import { CmsService } from './cms/cmsService';
|
||||
import { registerSearchServerCommand } from './objectExplorerNodeProvider/command';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||
@@ -120,6 +121,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<MssqlE
|
||||
vscode.window.showErrorMessage('Failed to start Sql tools service');
|
||||
});
|
||||
|
||||
registerSearchServerCommand(appContext);
|
||||
let contextProvider = new ContextProvider();
|
||||
context.subscriptions.push(contextProvider);
|
||||
context.subscriptions.push(credentialsStore);
|
||||
|
||||
@@ -174,3 +174,18 @@ export abstract class ProgressCommand extends Command {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function registerSearchServerCommand(appContext: AppContext): void {
|
||||
appContext.apiWrapper.registerCommand('mssql.searchServers', () => {
|
||||
vscode.window.showInputBox({
|
||||
placeHolder: localize('mssql.searchServers', 'Search Server Names')
|
||||
}).then((stringSearch) => {
|
||||
if (stringSearch) {
|
||||
vscode.commands.executeCommand('registeredServers.searchServer', (stringSearch));
|
||||
}
|
||||
});
|
||||
});
|
||||
appContext.apiWrapper.registerCommand('mssql.clearSearchServerResult', () => {
|
||||
vscode.commands.executeCommand('registeredServers.clearSearchServerResult');
|
||||
});
|
||||
}
|
||||
@@ -305,11 +305,20 @@ export class PreviewFileCommand extends ProgressCommand {
|
||||
await this.executeWithProgress(
|
||||
async (cancelToken: vscode.CancellationTokenSource) => {
|
||||
let contents = await fileNode.getFileContentsAsString(PreviewFileCommand.DefaultMaxSize);
|
||||
let doc = await this.openTextDocument(fspath.basename(fileNode.hdfsPath));
|
||||
let editor = await this.apiWrapper.showTextDocument(doc, vscode.ViewColumn.Active, false);
|
||||
await editor.edit(edit => {
|
||||
edit.insert(new vscode.Position(0, 0), contents);
|
||||
});
|
||||
let fileName: string = fspath.basename(fileNode.hdfsPath);
|
||||
if (fspath.extname(fileName) !== '.ipynb') {
|
||||
let doc = await this.openTextDocument(fileName);
|
||||
let editor = await this.apiWrapper.showTextDocument(doc, vscode.ViewColumn.Active, false);
|
||||
await editor.edit(edit => {
|
||||
edit.insert(new vscode.Position(0, 0), contents);
|
||||
});
|
||||
} else {
|
||||
let connectionProfile: azdata.IConnectionProfile = undefined;
|
||||
if (context.type === constants.ObjectExplorerService) {
|
||||
connectionProfile = context.explorerContext.connectionProfile;
|
||||
}
|
||||
await this.showNotebookDocument(fileName, connectionProfile, contents);
|
||||
}
|
||||
},
|
||||
localize('previewing', 'Generating preview'),
|
||||
false);
|
||||
@@ -322,6 +331,18 @@ export class PreviewFileCommand extends ProgressCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async showNotebookDocument(fileName: string, connectionProfile?: azdata.IConnectionProfile,
|
||||
initialContent?: string
|
||||
): Promise<azdata.nb.NotebookEditor> {
|
||||
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true)
|
||||
.with({ scheme: constants.UNTITLED_SCHEMA });
|
||||
return await azdata.nb.showNotebookDocument(docUri, {
|
||||
connectionProfile: connectionProfile,
|
||||
preview: false,
|
||||
initialContent: initialContent
|
||||
});
|
||||
}
|
||||
|
||||
private async openTextDocument(fileName: string): Promise<vscode.TextDocument> {
|
||||
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true);
|
||||
if (docUri) {
|
||||
|
||||
@@ -79,7 +79,11 @@ async function createSqlClusterConnInfo(sqlConnInfo: azdata.IConnectionProfile |
|
||||
let endpoints: IEndpoint[] = serverInfo.options[constants.clusterEndpointsProperty];
|
||||
if (!endpoints || endpoints.length === 0) { return undefined; }
|
||||
|
||||
let index = endpoints.findIndex(ep => ep.serviceName === constants.hadoopKnoxEndpointName);
|
||||
let index = endpoints.findIndex(ep => {
|
||||
let serviceName: string = ep.serviceName.toLowerCase();
|
||||
return serviceName === constants.hadoopEndpointNameKnox.toLowerCase() ||
|
||||
serviceName === constants.hadoopEndpointNameGateway.toLowerCase();
|
||||
});
|
||||
if (index < 0) { return undefined; }
|
||||
|
||||
let credentials = await azdata.connection.getCredentials(connectionId);
|
||||
|
||||
@@ -129,7 +129,7 @@ export class ConfigurePythonDialog {
|
||||
}
|
||||
|
||||
// Don't wait on installation, since there's currently no Cancel functionality
|
||||
this.jupyterInstallation.startInstallProcess(pythonLocation)
|
||||
this.jupyterInstallation.startInstallProcess(false, pythonLocation)
|
||||
.then(() => {
|
||||
this._setupComplete.resolve();
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as nls from 'vscode-nls';
|
||||
import { JupyterController } from './jupyter/jupyterController';
|
||||
import { AppContext } from './common/appContext';
|
||||
import { ApiWrapper } from './common/apiWrapper';
|
||||
import { IExtensionApi } from './types';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -20,10 +21,9 @@ const JUPYTER_NOTEBOOK_PROVIDER = 'jupyter';
|
||||
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
||||
const noNotebookVisible = localize('noNotebookVisible', 'No notebook editor is active');
|
||||
|
||||
let controller: JupyterController;
|
||||
|
||||
export let controller: JupyterController;
|
||||
|
||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', (context?: azdata.ConnectedContext) => {
|
||||
let connectionProfile: azdata.IConnectionProfile = undefined;
|
||||
if (context && context.connectionProfile) {
|
||||
@@ -49,7 +49,16 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
|
||||
let appContext = new AppContext(extensionContext, new ApiWrapper());
|
||||
controller = new JupyterController(appContext);
|
||||
controller.activate();
|
||||
let result = await controller.activate();
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
getJupyterController() {
|
||||
return controller;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function newNotebook(connectionProfile: azdata.IConnectionProfile) {
|
||||
@@ -75,8 +84,9 @@ function findNextUntitledEditorName(): string {
|
||||
// Note: this will go forever if it's coded wrong, or you have infinite Untitled notebooks!
|
||||
while (true) {
|
||||
let title = `Notebook-${nextVal}`;
|
||||
let hasTextDoc = vscode.workspace.textDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||
let hasNotebookDoc = azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||
if (!hasNotebookDoc) {
|
||||
if (!hasTextDoc && !hasNotebookDoc) {
|
||||
return title;
|
||||
}
|
||||
nextVal++;
|
||||
|
||||
30
extensions/notebook/src/integrationTest/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const path = require('path');
|
||||
const testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
const suite = 'Notebook Extension Integration Tests';
|
||||
|
||||
const options: any = {
|
||||
ui: 'bdd',
|
||||
useColors: true,
|
||||
timeout: 600000
|
||||
};
|
||||
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
options.reporter = 'mocha-multi-reporters';
|
||||
options.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testRunner.configure(options);
|
||||
|
||||
export = testRunner;
|
||||
@@ -6,16 +6,17 @@
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as tempWrite from 'temp-write';
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
|
||||
import { JupyterController } from '../jupyter/jupyterController';
|
||||
import { INotebook, CellTypes } from '../contracts/content';
|
||||
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
|
||||
|
||||
describe('Notebook Integration Test', function (): void {
|
||||
describe('Notebook Extension Integration Tests', function () {
|
||||
this.timeout(600000);
|
||||
|
||||
let expectedNotebookContent: INotebook = {
|
||||
@@ -35,12 +36,34 @@ describe('Notebook Integration Test', function (): void {
|
||||
nbformat_minor: 2
|
||||
};
|
||||
|
||||
let installComplete = false;
|
||||
let pythonInstallDir = process.env.PYTHON_TEST_PATH;
|
||||
let jupyterController: JupyterController;
|
||||
before(async function () {
|
||||
assert.ok(pythonInstallDir, 'Python install directory was not defined.');
|
||||
|
||||
let notebookExtension: vscode.Extension<any>;
|
||||
while (true) {
|
||||
notebookExtension = vscode.extensions.getExtension('Microsoft.notebook');
|
||||
if (notebookExtension && notebookExtension.isActive) {
|
||||
break;
|
||||
} else {
|
||||
await new Promise(resolve => { setTimeout(resolve, 1000); });
|
||||
}
|
||||
}
|
||||
|
||||
jupyterController = notebookExtension.exports.getJupyterController() as JupyterController;
|
||||
|
||||
await jupyterController.jupyterInstallation.startInstallProcess(false, pythonInstallDir);
|
||||
installComplete = true;
|
||||
});
|
||||
|
||||
it('Should connect to local notebook server with result 2', async function () {
|
||||
this.timeout(60000);
|
||||
should(installComplete).be.true('Python setup did not complete.');
|
||||
should(JupyterServerInstallation.getPythonInstallPath(jupyterController.jupyterInstallation.apiWrapper)).be.equal(pythonInstallDir);
|
||||
|
||||
let pythonNotebook = Object.assign({}, expectedNotebookContent, { metadata: { kernelspec: { name: 'python3', display_name: 'Python 3' } } });
|
||||
let uri = writeNotebookToFile(pythonNotebook);
|
||||
await ensureJupyterInstalled();
|
||||
|
||||
let notebook = await azdata.nb.showNotebookDocument(uri);
|
||||
should(notebook.document.cells).have.length(1);
|
||||
@@ -51,82 +74,13 @@ describe('Notebook Integration Test', function (): void {
|
||||
let result = (<azdata.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 connectionProfile = await connectToSparkIntegrationServer();
|
||||
|
||||
// When I open a Spark notebook and run the cell
|
||||
let notebook = await azdata.nb.showNotebookDocument(uri, {
|
||||
connectionProfile: connectionProfile
|
||||
});
|
||||
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 = (<azdata.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) { }
|
||||
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
||||
});
|
||||
});
|
||||
|
||||
async function connectToSparkIntegrationServer(): Promise<azdata.IConnectionProfile> {
|
||||
assert.ok(process.env.BACKEND_HOSTNAME, 'BACKEND_HOSTNAME, BACKEND_USERNAME, BACKEND_PWD must be set using ./tasks/setbackenvariables.sh or .\\tasks\\setbackendvaraibles.bat');
|
||||
let connInfo: azdata.connection.Connection = {
|
||||
options: {
|
||||
'host': process.env.BACKEND_HOSTNAME,
|
||||
'groupId': 'C777F06B-202E-4480-B475-FA416154D458',
|
||||
'knoxport': '',
|
||||
'user': process.env.BACKEND_USERNAME,
|
||||
'password': process.env.BACKEND_PWD
|
||||
},
|
||||
providerName: 'HADOOP_KNOX',
|
||||
connectionId: 'abcd1234',
|
||||
};
|
||||
connInfo['savePassword'] = true;
|
||||
let result = await azdata.connection.connect(<any>connInfo as azdata.IConnectionProfile);
|
||||
|
||||
should(result.connected).be.true();
|
||||
should(result.connectionId).not.be.undefined();
|
||||
should(result.connectionId).not.be.empty();
|
||||
should(result.errorMessage).be.undefined();
|
||||
|
||||
let activeConnections = await azdata.connection.getActiveConnections();
|
||||
should(activeConnections).have.length(1);
|
||||
|
||||
return <azdata.IConnectionProfile><any>connInfo;
|
||||
}
|
||||
|
||||
function writeNotebookToFile(pythonNotebook: INotebook): vscode.Uri {
|
||||
let notebookContentString = JSON.stringify(pythonNotebook);
|
||||
let localFile = tempWrite.sync(notebookContentString, 'notebook.ipynb');
|
||||
let uri = vscode.Uri.file(localFile);
|
||||
return uri;
|
||||
}
|
||||
|
||||
async function ensureJupyterInstalled(): Promise<void> {
|
||||
let jupterControllerExports = vscode.extensions.getExtension('Microsoft.sql-vnext').exports;
|
||||
let jupyterController = jupterControllerExports.getJupterController() as JupyterController;
|
||||
await jupyterController.jupyterInstallation.installReady;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -175,13 +175,13 @@ export class JupyterController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
private async handleDependenciesReinstallation(): Promise<void> {
|
||||
if (await this.confirmReinstall()) {
|
||||
this._jupyterInstallation = await JupyterServerInstallation.getInstallation(
|
||||
this.extensionContext.extensionPath,
|
||||
this.outputChannel,
|
||||
this.apiWrapper,
|
||||
undefined,
|
||||
true);
|
||||
try {
|
||||
let doReinstall = await this.confirmReinstall();
|
||||
if (doReinstall) {
|
||||
await this._jupyterInstallation.startInstallProcess(true);
|
||||
}
|
||||
} catch (err) {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,12 @@ export class JupyterKernel implements nb.IKernel {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get requiresConnection(): boolean {
|
||||
// TODO would be good to have a smarter way to do this.
|
||||
// for now only Spark kernels need a connection
|
||||
return !!(this.kernelImpl.name && this.kernelImpl.name.toLowerCase().indexOf('spark') > -1);
|
||||
}
|
||||
|
||||
public get isReady(): boolean {
|
||||
return this.kernelImpl.isReady;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ const msgPythonUnpackError = localize('msgPythonUnpackError', 'Error while unpac
|
||||
const msgTaskName = localize('msgTaskName', 'Installing Notebook dependencies');
|
||||
const msgInstallPkgStart = localize('msgInstallPkgStart', 'Installing Notebook dependencies, see Tasks view for more information');
|
||||
const msgInstallPkgFinish = localize('msgInstallPkgFinish', 'Notebook dependencies installation is complete');
|
||||
const msgPythonRunningError = localize('msgPythonRunningError', 'Cannot overwrite existing Python installation while python is running.');
|
||||
const msgPendingInstallError = localize('msgPendingInstallError', 'Another Python installation is currently in progress.');
|
||||
function msgDependenciesInstallationFailed(errorMessage: string): string { return localize('msgDependenciesInstallationFailed', 'Installing Notebook dependencies failed with error: {0}', errorMessage); }
|
||||
function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', 'Downloading local python for platform: {0} to {1}', platform, pythonDownloadUrl); }
|
||||
|
||||
@@ -52,41 +54,19 @@ export default class JupyterServerInstallation {
|
||||
|
||||
// Allows dependencies to be installed even if an existing installation is already present
|
||||
private _forceInstall: boolean;
|
||||
private _installInProgress: boolean;
|
||||
|
||||
private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
|
||||
|
||||
private _installReady: Deferred<void>;
|
||||
|
||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string, forceInstall?: boolean) {
|
||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
||||
this.extensionPath = extensionPath;
|
||||
this.outputChannel = outputChannel;
|
||||
this.apiWrapper = apiWrapper;
|
||||
this._pythonInstallationPath = pythonInstallationPath || JupyterServerInstallation.getPythonInstallPath(this.apiWrapper);
|
||||
this._forceInstall = !!forceInstall;
|
||||
this._forceInstall = false;
|
||||
this._installInProgress = false;
|
||||
|
||||
this.configurePackagePaths();
|
||||
|
||||
this._installReady = new Deferred<void>();
|
||||
if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
||||
this._installReady.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
public get installReady(): Promise<void> {
|
||||
return this._installReady.promise;
|
||||
}
|
||||
|
||||
public static async getInstallation(
|
||||
extensionPath: string,
|
||||
outputChannel: OutputChannel,
|
||||
apiWrapper: ApiWrapper,
|
||||
pythonInstallationPath?: string,
|
||||
forceInstall?: boolean): Promise<JupyterServerInstallation> {
|
||||
|
||||
let installation = new JupyterServerInstallation(extensionPath, outputChannel, apiWrapper, pythonInstallationPath, forceInstall);
|
||||
await installation.startInstallProcess();
|
||||
|
||||
return installation;
|
||||
}
|
||||
|
||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation): Promise<void> {
|
||||
@@ -217,7 +197,7 @@ export default class JupyterServerInstallation {
|
||||
// Update python paths and properties to reference user's local python.
|
||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||
|
||||
this._pythonExecutable = path.join(pythonSourcePath, process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
||||
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath);
|
||||
this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix);
|
||||
|
||||
// Store paths to python libraries required to run jupyter.
|
||||
@@ -230,6 +210,11 @@ export default class JupyterServerInstallation {
|
||||
}
|
||||
this.pythonEnvVarPath = this.pythonBinPath + delimiter + this.pythonEnvVarPath;
|
||||
|
||||
// Delete existing Python variables in ADS to prevent conflict with other installs
|
||||
delete process.env['PYTHONPATH'];
|
||||
delete process.env['PYTHONSTARTUP'];
|
||||
delete process.env['PYTHONHOME'];
|
||||
|
||||
// Store the executable options to run child processes with env var without interfering parent env var.
|
||||
let env = Object.assign({}, process.env);
|
||||
delete env['Path']; // Delete extra 'Path' variable for Windows, just in case.
|
||||
@@ -239,15 +224,51 @@ export default class JupyterServerInstallation {
|
||||
};
|
||||
}
|
||||
|
||||
public startInstallProcess(pythonInstallationPath?: string): Promise<void> {
|
||||
if (pythonInstallationPath) {
|
||||
this._pythonInstallationPath = pythonInstallationPath;
|
||||
this.configurePackagePaths();
|
||||
private isPythonRunning(pythonInstallPath: string): Promise<boolean> {
|
||||
let pythonExePath = JupyterServerInstallation.getPythonExePath(pythonInstallPath);
|
||||
return new Promise<boolean>(resolve => {
|
||||
fs.open(pythonExePath, 'r+', (err, fd) => {
|
||||
if (!err) {
|
||||
fs.close(fd, err => {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(err.code === 'EBUSY' || err.code === 'EPERM');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs Python and associated dependencies to the specified directory.
|
||||
* @param forceInstall Indicates whether an existing installation should be overwritten, if it exists.
|
||||
* @param installationPath Optional parameter that specifies where to install python.
|
||||
* The previous path (or the default) is used if a new path is not specified.
|
||||
*/
|
||||
public async startInstallProcess(forceInstall: boolean, installationPath?: string): Promise<void> {
|
||||
let isPythonRunning = await this.isPythonRunning(installationPath ? installationPath : this._pythonInstallationPath);
|
||||
if (isPythonRunning) {
|
||||
return Promise.reject(msgPythonRunningError);
|
||||
}
|
||||
let updateConfig = () => {
|
||||
|
||||
if (this._installInProgress) {
|
||||
return Promise.reject(msgPendingInstallError);
|
||||
}
|
||||
this._installInProgress = true;
|
||||
|
||||
this._forceInstall = forceInstall;
|
||||
if (installationPath) {
|
||||
this._pythonInstallationPath = installationPath;
|
||||
}
|
||||
this.configurePackagePaths();
|
||||
|
||||
let updateConfig = async () => {
|
||||
let notebookConfig = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||
notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, ConfigurationTarget.Global);
|
||||
await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, ConfigurationTarget.Global);
|
||||
};
|
||||
|
||||
let installReady = new Deferred<void>();
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall) {
|
||||
this.apiWrapper.startBackgroundOperation({
|
||||
displayName: msgTaskName,
|
||||
@@ -255,27 +276,32 @@ export default class JupyterServerInstallation {
|
||||
isCancelable: false,
|
||||
operation: op => {
|
||||
this.installDependencies(op)
|
||||
.then(() => {
|
||||
this._installReady.resolve();
|
||||
updateConfig();
|
||||
.then(async () => {
|
||||
await updateConfig();
|
||||
installReady.resolve();
|
||||
this._installInProgress = false;
|
||||
})
|
||||
.catch(err => {
|
||||
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
|
||||
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
|
||||
this.apiWrapper.showErrorMessage(errorMsg);
|
||||
this._installReady.reject(errorMsg);
|
||||
installReady.reject(errorMsg);
|
||||
this._installInProgress = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Python executable already exists, but the path setting wasn't defined,
|
||||
// so update it here
|
||||
this._installReady.resolve();
|
||||
updateConfig();
|
||||
await updateConfig();
|
||||
installReady.resolve();
|
||||
}
|
||||
return this._installReady.promise;
|
||||
return installReady.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog for configuring the installation path for the Notebook Python dependencies.
|
||||
*/
|
||||
public async promptForPythonInstall(): Promise<void> {
|
||||
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, this);
|
||||
@@ -312,6 +338,10 @@ export default class JupyterServerInstallation {
|
||||
return this._pythonExecutable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a python executable exists at the "notebook.pythonPath" defined in the user's settings.
|
||||
* @param apiWrapper An ApiWrapper to use when retrieving user settings info.
|
||||
*/
|
||||
public static isPythonInstalled(apiWrapper: ApiWrapper): boolean {
|
||||
// Don't use _pythonExecutable here, since it could be populated with a default value
|
||||
let pathSetting = JupyterServerInstallation.getPythonPathSetting(apiWrapper);
|
||||
@@ -319,13 +349,15 @@ export default class JupyterServerInstallation {
|
||||
return false;
|
||||
}
|
||||
|
||||
let pythonExe = path.join(
|
||||
pathSetting,
|
||||
constants.pythonBundleVersion,
|
||||
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
||||
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting);
|
||||
return fs.existsSync(pythonExe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Python installation path defined in "notebook.pythonPath" in the user's settings.
|
||||
* Returns a default path if the setting is not defined.
|
||||
* @param apiWrapper An ApiWrapper to use when retrieving user settings info.
|
||||
*/
|
||||
public static getPythonInstallPath(apiWrapper: ApiWrapper): string {
|
||||
let userPath = JupyterServerInstallation.getPythonPathSetting(apiWrapper);
|
||||
return userPath ? userPath : JupyterServerInstallation.DefaultPythonLocation;
|
||||
@@ -345,6 +377,11 @@ export default class JupyterServerInstallation {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the folder containing the python executable under the path defined in
|
||||
* "notebook.pythonPath" in the user's settings.
|
||||
* @param apiWrapper An ApiWrapper to use when retrieving user settings info.
|
||||
*/
|
||||
public static getPythonBinPath(apiWrapper: ApiWrapper): string {
|
||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||
|
||||
@@ -353,4 +390,11 @@ export default class JupyterServerInstallation {
|
||||
constants.pythonBundleVersion,
|
||||
pythonBinPathSuffix);
|
||||
}
|
||||
|
||||
private static getPythonExePath(pythonInstallPath: string): string {
|
||||
return path.join(
|
||||
pythonInstallPath,
|
||||
constants.pythonBundleVersion,
|
||||
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,8 @@ const configBase = {
|
||||
|
||||
const KNOX_ENDPOINT_SERVER = 'host';
|
||||
const KNOX_ENDPOINT_PORT = 'knoxport';
|
||||
const KNOX_ENDPOINT = 'knox';
|
||||
const KNOX_ENDPOINT_KNOX = 'knox';
|
||||
const KNOX_ENDPOINT_GATEWAY = 'gateway';
|
||||
const SQL_PROVIDER = 'MSSQL';
|
||||
const USER = 'user';
|
||||
const DEFAULT_CLUSTER_USER_NAME = 'root';
|
||||
@@ -242,7 +243,9 @@ export class JupyterSession implements nb.ISession {
|
||||
|
||||
//Update server info with bigdata endpoint - Unified Connection
|
||||
if (connection.providerName === SQL_PROVIDER) {
|
||||
let clusterEndpoint: utils.IEndpoint = await this.getClusterEndpoint(connection.id, KNOX_ENDPOINT);
|
||||
let clusterEndpoint: utils.IEndpoint =
|
||||
await this.getClusterEndpoint(connection.id, KNOX_ENDPOINT_KNOX) ||
|
||||
await this.getClusterEndpoint(connection.id, KNOX_ENDPOINT_GATEWAY);
|
||||
if (!clusterEndpoint) {
|
||||
let kernelDisplayName: string = await this.getKernelDisplayName();
|
||||
return Promise.reject(new Error(localize('connectionNotValid', 'Spark kernels require a connection to a SQL Server big data cluster master instance.')));
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// import * as vscode from 'vscode';
|
||||
// import { context } from './testContext';
|
||||
|
||||
const path = require('path');
|
||||
const testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
16
extensions/notebook/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { JupyterController } from './jupyter/jupyterController';
|
||||
|
||||
/**
|
||||
* The API provided by this extension.
|
||||
*
|
||||
* @export
|
||||
* @interface IExtensionApi
|
||||
*/
|
||||
export interface IExtensionApi {
|
||||
getJupyterController(): JupyterController;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"description": "Dependencies shared by all extensions",
|
||||
"dependencies": {
|
||||
"typescript": "3.4.1"
|
||||
"typescript": "3.4.3"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node ./postinstall"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
typescript@3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
|
||||
integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
|
||||
typescript@3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
|
||||
integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==
|
||||
|
||||
@@ -4,10 +4,14 @@ pushd %~dp0\..
|
||||
|
||||
set VSCODEUSERDATADIR=%TMP%\adsuser-%RANDOM%-%TIME:~6,5%
|
||||
set VSCODEEXTENSIONSDIR=%TMP%\adsext-%RANDOM%-%TIME:~6,5%
|
||||
set PYTHON_TEST_PATH=%VSCODEUSERDATADIR%\TestPythonInstallation
|
||||
echo %VSCODEUSERDATADIR%
|
||||
echo %VSCODEEXTENSIONSDIR%
|
||||
echo %PYTHON_TEST_PATH%
|
||||
@echo OFF
|
||||
|
||||
:: This first notebook test will do the python install that the later tests depend on
|
||||
call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\notebook --extensionTestsPath=%~dp0\..\extensions\notebook\out\integrationTest --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222
|
||||
call .\scripts\code.bat --extensionDevelopmentPath=%~dp0\..\extensions\integration-tests --extensionTestsPath=%~dp0\..\extensions\integration-tests\out --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222
|
||||
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
@@ -12,12 +12,15 @@ else
|
||||
VSCODEEXTDIR=`mktemp -d 2>/dev/null`
|
||||
fi
|
||||
|
||||
export PYTHON_TEST_PATH=$VSCODEUSERDATADIR/TestPythonInstallation
|
||||
|
||||
cd $ROOT
|
||||
echo $VSCODEUSERDATADIR
|
||||
echo $VSCODEEXTDIR
|
||||
echo $PYTHON_TEST_PATH
|
||||
|
||||
./scripts/code.sh --extensionDevelopmentPath=$ROOT/extensions/notebook --extensionTestsPath=$ROOT/extensions/notebook/out/integrationTest --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR --remote-debugging-port=9222
|
||||
./scripts/code.sh --extensionDevelopmentPath=$ROOT/extensions/integration-tests --extensionTestsPath=$ROOT/extensions/integration-tests/out --user-data-dir=$VSCODEUSERDATADIR --extensions-dir=$VSCODEEXTDIR --remote-debugging-port=9222
|
||||
|
||||
|
||||
rm -r $VSCODEUSERDATADIR
|
||||
rm -r -f $VSCODEUSERDATADIR
|
||||
rm -r $VSCODEEXTDIR
|
||||
|
||||
6
src/sql/azdata.proposed.d.ts
vendored
@@ -4085,6 +4085,11 @@ declare module 'azdata' {
|
||||
* Default kernel for notebook
|
||||
*/
|
||||
defaultKernel?: nb.IKernelSpec;
|
||||
|
||||
/**
|
||||
* Optional content used to give an initial notebook state
|
||||
*/
|
||||
initialContent?: nb.INotebookContents | string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4475,6 +4480,7 @@ declare module 'azdata' {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly supportsIntellisense: boolean;
|
||||
readonly requiresConnection?: boolean;
|
||||
/**
|
||||
* Test whether the kernel is ready.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
.monaco-table * {
|
||||
box-sizing: border-box;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.monaco-table {
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
|
||||
.list-row.account-picker-list .label .contextual-display-name {
|
||||
font-size: 15px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.list-row.account-picker-list .label .display-name {
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
.list-row.account-picker-list .label .content {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||
import { NotebookInput } from 'sql/workbench/parts/notebook/notebookInput';
|
||||
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { notebookModeId } from 'sql/common/constants';
|
||||
|
||||
@@ -77,7 +77,7 @@ let defaultVal = [
|
||||
{
|
||||
name: 'Tasks',
|
||||
widget: {
|
||||
'tasks-widget': [{ name: 'restore', when: '!mssql:iscloud' }, 'configureDashboard', 'newQuery']
|
||||
'tasks-widget': [{ name: 'restore', when: '!mssql:iscloud' }, 'configureDashboard', 'newQuery', 'mssqlCluster.task.newNotebook']
|
||||
},
|
||||
gridItemConfig: {
|
||||
sizex: 1,
|
||||
|
||||
@@ -82,6 +82,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
|
||||
let actionContext = {
|
||||
ownerUri: ownerUri,
|
||||
targetObject: targetObject
|
||||
|
||||
};
|
||||
|
||||
let anchor = { x: event.pageX + 1, y: event.pageY };
|
||||
@@ -116,7 +117,8 @@ export abstract class JobManagementView extends TabChild implements AfterContent
|
||||
{ action: refreshAction },
|
||||
{ action: newAction }
|
||||
]);
|
||||
this._actionBar.context = { component: this };
|
||||
let context: IJobActionInfo = { component: this };
|
||||
this._actionBar.context = context;
|
||||
}
|
||||
|
||||
public refreshJobs() {
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tree-component-node-tile .model-view-tree-node-item-icon{
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
.tree-component-node-tile .model-view-tree-node-item-icon {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tree-component-node-tile .model-view-tree-node-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -147,6 +147,8 @@ export class TreeComponentRenderer extends Disposable implements IRenderer {
|
||||
const icon = this.themeService.getTheme().type === LIGHT ? element.icon : element.iconDark;
|
||||
const iconUri = icon ? URI.revive(icon) : null;
|
||||
templateData.icon.style.backgroundImage = iconUri ? `url('${iconUri.toString(true)}')` : '';
|
||||
templateData.icon.style.backgroundRepeat = 'no-repeat';
|
||||
templateData.icon.style.backgroundPosition = 'center';
|
||||
dom.toggleClass(templateData.icon, 'model-view-tree-node-item-icon', !!icon);
|
||||
if (element) {
|
||||
element.onCheckedChanged = (checked: boolean) => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./media/serverTreeActions';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as builder from 'sql/base/browser/builder';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -33,6 +33,7 @@ import { SERVER_GROUP_CONFIG, SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/parts/o
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serverTreeActionProvider';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
const $ = builder.$;
|
||||
|
||||
@@ -72,6 +73,7 @@ export class ServerTreeView {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
}
|
||||
});
|
||||
this.registerCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +98,25 @@ export class ServerTreeView {
|
||||
return this._tree;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Register search related commands
|
||||
*/
|
||||
public registerCommands(): void {
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'registeredServers.searchServer',
|
||||
handler: (accessor: ServicesAccessor, ...args: any[]) => {
|
||||
this.searchTree(args[0]);
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'registeredServers.clearSearchServerResult',
|
||||
handler: (accessor: ServicesAccessor, ...args: any[]) => {
|
||||
this.refreshTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the view body
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Dimension, $, getContentHeight, getContentWidth } from 'vs/base/browser/dom';
|
||||
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Builder } from 'sql/base/browser/builder';
|
||||
@@ -211,8 +211,13 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
}
|
||||
|
||||
private buildOptions() {
|
||||
dispose(this.optionDisposables);
|
||||
this.optionDisposables = [];
|
||||
// The first element in the disposables list is for the chart type: the master dropdown that controls other option controls.
|
||||
// whiling rebuilding the options we should not dispose it, otherwise it would react to the theme change event
|
||||
if (this.optionDisposables.length > 1) {
|
||||
dispose(this.optionDisposables.slice(1));
|
||||
this.optionDisposables.splice(1);
|
||||
}
|
||||
|
||||
this.optionMap = {
|
||||
'type': this.optionMap['type']
|
||||
};
|
||||
@@ -284,7 +289,7 @@ export class ChartView extends Disposable implements IPanelView {
|
||||
};
|
||||
break;
|
||||
case ControlType.combo:
|
||||
let dropdown = new SelectBox(option.displayableOptions || option.options, 0, this._contextViewService);
|
||||
let dropdown = new SelectBox(option.displayableOptions || option.options, undefined, this._contextViewService);
|
||||
dropdown.select(option.options.indexOf(value));
|
||||
dropdown.render(optionContainer);
|
||||
dropdown.onDidSelect(e => {
|
||||
|
||||
@@ -30,8 +30,8 @@ export enum JobActions {
|
||||
}
|
||||
|
||||
export class IJobActionInfo {
|
||||
ownerUri: string;
|
||||
targetObject: any;
|
||||
ownerUri?: string;
|
||||
targetObject?: any;
|
||||
component: JobManagementView;
|
||||
}
|
||||
|
||||
@@ -69,10 +69,11 @@ export class NewJobAction extends Action {
|
||||
super(NewJobAction.ID, NewJobAction.LABEL, 'newStepIcon');
|
||||
}
|
||||
|
||||
public run(context: JobsViewComponent): Promise<boolean> {
|
||||
public run(context: IJobActionInfo): Promise<boolean> {
|
||||
let component = context.component as JobsViewComponent;
|
||||
return new Promise<boolean>(async (resolve, reject) => {
|
||||
try {
|
||||
await context.openCreateJobDialog();
|
||||
await component.openCreateJobDialog();
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@@ -293,10 +294,11 @@ export class NewAlertAction extends Action {
|
||||
super(NewAlertAction.ID, NewAlertAction.LABEL, 'newStepIcon');
|
||||
}
|
||||
|
||||
public run(context: AlertsViewComponent): Promise<boolean> {
|
||||
public run(context: IJobActionInfo): Promise<boolean> {
|
||||
let component = context.component as AlertsViewComponent;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
context.openCreateAlertDialog();
|
||||
component.openCreateAlertDialog();
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@@ -380,10 +382,11 @@ export class NewOperatorAction extends Action {
|
||||
super(NewOperatorAction.ID, NewOperatorAction.LABEL, 'newStepIcon');
|
||||
}
|
||||
|
||||
public run(context: OperatorsViewComponent): Promise<boolean> {
|
||||
public run(context: IJobActionInfo): Promise<boolean> {
|
||||
let component = context.component as OperatorsViewComponent;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
context.openCreateOperatorDialog();
|
||||
component.openCreateOperatorDialog();
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@@ -466,10 +469,11 @@ export class NewProxyAction extends Action {
|
||||
super(NewProxyAction.ID, NewProxyAction.LABEL, 'newStepIcon');
|
||||
}
|
||||
|
||||
public run(context: ProxiesViewComponent): Promise<boolean> {
|
||||
public run(context: IJobActionInfo): Promise<boolean> {
|
||||
let component = context.component as ProxiesViewComponent;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
context.openCreateProxyDialog();
|
||||
component.openCreateProxyDialog();
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
|
||||
@@ -13,42 +13,156 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
||||
export class SqlTelemetryContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super();
|
||||
|
||||
const dailyLastUseDate = Date.parse(storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||
const weeklyLastUseDate = Date.parse(storageService.get('telemetry.weeklyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||
const monthlyLastUseDate = Date.parse(storageService.get('telemetry.monthlyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||
const dailyLastUseDate: number = Date.parse(storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||
const weeklyLastUseDate: number = Date.parse(storageService.get('telemetry.weeklyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||
const monthlyLastUseDate: number = Date.parse(storageService.get('telemetry.monthlyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||
const firstTimeUser: boolean = dailyLastUseDate === Date.parse('0');
|
||||
|
||||
let today = new Date().toUTCString();
|
||||
let todayString: string = new Date().toUTCString();
|
||||
|
||||
// daily user event
|
||||
if (this.diffInDays(Date.parse(today), dailyLastUseDate) >= 1) {
|
||||
if (this.didDayChange(dailyLastUseDate)) {
|
||||
// daily first use
|
||||
telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true });
|
||||
storageService.store('telemetry.dailyLastUseDate', today, StorageScope.GLOBAL);
|
||||
storageService.store('telemetry.dailyLastUseDate', todayString, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
// weekly user event
|
||||
if (this.diffInDays(Date.parse(today), weeklyLastUseDate) >= 7) {
|
||||
if (this.didWeekChange(weeklyLastUseDate)) {
|
||||
// weekly first use
|
||||
telemetryService.publicLog('telemetry.weeklyFirstUse', { weeklyFirstUse: true });
|
||||
storageService.store('telemetry.weeklyLastUseDate', today, StorageScope.GLOBAL);
|
||||
storageService.store('telemetry.weeklyLastUseDate', todayString, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
// monthly user events
|
||||
if (this.diffInDays(Date.parse(today), monthlyLastUseDate) >= 30) {
|
||||
|
||||
/* send monthly uses once the user launches on a day that's in a month
|
||||
after the last time we sent a monthly usage count */
|
||||
const monthlyUseCount: number = storageService.getNumber('telemetry.monthlyUseCount', StorageScope.GLOBAL, 0);
|
||||
if (this.didMonthChange(monthlyLastUseDate)) {
|
||||
telemetryService.publicLog('telemetry.monthlyUse', { monthlyFirstUse: true });
|
||||
storageService.store('telemetry.monthlyLastUseDate', today, StorageScope.GLOBAL);
|
||||
}
|
||||
// the month changed, so send the user usage type event based on monthly count for last month
|
||||
// and reset the count for this month
|
||||
let lastMonthDate = new Date(monthlyLastUseDate);
|
||||
this.sendUsageEvent(monthlyUseCount, lastMonthDate);
|
||||
|
||||
const wasActiveLastMonth: boolean = storageService.getBoolean('telemetry.wasActiveLastMonth', StorageScope.GLOBAL, false);
|
||||
|
||||
if (firstTimeUser) {
|
||||
// new user
|
||||
this.sendGrowthTypeEvent(UserGrowthType.NewUser, lastMonthDate);
|
||||
}
|
||||
|
||||
// continuing or returning user
|
||||
this.sendGrowthTypeEvent(wasActiveLastMonth ? UserGrowthType.ContinuingUser : UserGrowthType.ReturningUser, lastMonthDate);
|
||||
|
||||
// set wasActiveUserLastMonth
|
||||
storageService.store('telemetry.wasActiveLastMonth', true, StorageScope.GLOBAL);
|
||||
|
||||
// reset the monthly count for the new month
|
||||
storageService.store('telemetry.monthlyUseCount', 1, StorageScope.GLOBAL);
|
||||
storageService.store('telemetry.monthlyLastUseDate', todayString, StorageScope.GLOBAL);
|
||||
} else {
|
||||
// if it's the same month, increment the monthly use count
|
||||
storageService.store('telemetry.monthlyUseCount', monthlyUseCount + 1, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
private didDayChange(lastUseDateNumber: number): boolean {
|
||||
let nowDateNumber: number = Date.parse(new Date().toUTCString());
|
||||
if (this.diffInDays(nowDateNumber, lastUseDateNumber) >= 1) {
|
||||
return true;
|
||||
} else {
|
||||
let nowDate = new Date(nowDateNumber);
|
||||
let lastUseDate = new Date(lastUseDateNumber);
|
||||
return nowDate.getUTCDay() !== lastUseDate.getUTCDay();
|
||||
}
|
||||
}
|
||||
|
||||
private didWeekChange(lastUseDateNumber: number): boolean {
|
||||
let nowDateNumber: number = Date.parse(new Date().toUTCString());
|
||||
if (this.diffInDays(nowDateNumber, lastUseDateNumber) >= 7) {
|
||||
return true;
|
||||
} else {
|
||||
let nowDate = new Date(nowDateNumber);
|
||||
let lastUseDate = new Date(lastUseDateNumber);
|
||||
return nowDate.getUTCDay() < lastUseDate.getUTCDay();
|
||||
}
|
||||
}
|
||||
|
||||
private didMonthChange(lastUseDateNumber: number): boolean {
|
||||
let nowDateNumber: number = Date.parse(new Date().toUTCString());
|
||||
if (this.diffInDays(nowDateNumber, lastUseDateNumber) >= 30) {
|
||||
return true;
|
||||
} else {
|
||||
let nowDate = new Date(nowDateNumber);
|
||||
let lastUseDate = new Date(lastUseDateNumber);
|
||||
return nowDate.getUTCMonth() !== lastUseDate.getUTCMonth();
|
||||
}
|
||||
}
|
||||
|
||||
private diffInDays(nowDate: number, lastUseDate: number): number {
|
||||
return (nowDate - lastUseDate) / (24 * 3600 * 1000);
|
||||
return (nowDate - lastUseDate) / (3600 * 1000 * 24);
|
||||
}
|
||||
|
||||
// Usage Metrics
|
||||
private sendUsageEvent(monthlyUseCount: number, lastMonthDate: Date): void {
|
||||
let userUsageType: UserUsageType | undefined;
|
||||
if (monthlyUseCount === 1) {
|
||||
userUsageType = UserUsageType.TireKicker;
|
||||
} else if (monthlyUseCount >= 2 && monthlyUseCount <= 11) {
|
||||
userUsageType = UserUsageType.Occasional;
|
||||
} else if (monthlyUseCount >= 12 && monthlyUseCount <= 20) {
|
||||
userUsageType = UserUsageType.Engaged;
|
||||
} else if (monthlyUseCount > 20) {
|
||||
userUsageType = UserUsageType.Dedicated;
|
||||
}
|
||||
if (userUsageType) {
|
||||
this.telemetryService.publicLog('telemetry.userUsage',
|
||||
{ userType: userUsageType, monthlyUseCount: monthlyUseCount, month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() });
|
||||
}
|
||||
}
|
||||
|
||||
// Growth Metrics
|
||||
private sendGrowthTypeEvent(growthType: UserGrowthType, lastMonthDate: Date): void {
|
||||
this.telemetryService.publicLog('telemetry.userGrowthType', {
|
||||
userGrowthType: growthType, month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Growth Metrics
|
||||
Active here means opened app atleast 1 time in a month
|
||||
*/
|
||||
export enum UserGrowthType {
|
||||
// first time opening app
|
||||
NewUser = 1,
|
||||
// was active before, wasn't active last month, but is active this month
|
||||
ReturningUser = 2,
|
||||
// was active last month and this month
|
||||
ContinuingUser = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage Metrics
|
||||
* TireKicker = 1 day/month
|
||||
* Occasional = 2-11 days/month
|
||||
* Engaged = 12-20 days/month
|
||||
* Dedicated = 20+ days/month
|
||||
*/
|
||||
export enum UserUsageType {
|
||||
/* 1 day per month */
|
||||
TireKicker = 1,
|
||||
/* 2-11 days per month */
|
||||
Occasional = 2,
|
||||
/* 12-20 days per month */
|
||||
Engaged = 3,
|
||||
/* 20+ days per month */
|
||||
Dedicated = 4
|
||||
}
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SqlTelemetryContribution, LifecyclePhase.Starting);
|
||||
|
||||
@@ -463,6 +463,7 @@ export interface INotebookKernelDetails {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly supportsIntellisense: boolean;
|
||||
readonly requiresConnection: boolean;
|
||||
readonly info?: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
id: kernel.id,
|
||||
info: kernel.info,
|
||||
name: kernel.name,
|
||||
supportsIntellisense: kernel.supportsIntellisense
|
||||
supportsIntellisense: kernel.supportsIntellisense,
|
||||
requiresConnection: kernel.requiresConnection
|
||||
};
|
||||
return kernelDetails;
|
||||
}
|
||||
|
||||
@@ -169,6 +169,13 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
|
||||
options.providerId = showOptions.providerId;
|
||||
options.connectionProfile = showOptions.connectionProfile;
|
||||
options.defaultKernel = showOptions.defaultKernel;
|
||||
if (showOptions.initialContent) {
|
||||
if (typeof (showOptions.initialContent) !== 'string') {
|
||||
options.initialContent = JSON.stringify(showOptions.initialContent);
|
||||
} else {
|
||||
options.initialContent = showOptions.initialContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
let id = await this._proxy.$tryShowNotebookDocument(uri, options);
|
||||
let editor = this.getEditor(id);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { INotebookService, INotebookProvider, INotebookManager } from 'sql/workb
|
||||
import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, FutureMessageType, INotebookFutureDetails, INotebookFutureDone } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { FutureInternal } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
|
||||
@@ -352,6 +352,10 @@ class KernelWrapper implements azdata.nb.IKernel {
|
||||
return this.kernelDetails.supportsIntellisense;
|
||||
}
|
||||
|
||||
get requiresConnection(): boolean {
|
||||
return this.kernelDetails.requiresConnection;
|
||||
}
|
||||
|
||||
get info(): azdata.nb.IInfoReply {
|
||||
return this._info;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ import {
|
||||
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
|
||||
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
||||
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||
import { NotebookInput } from 'sql/workbench/parts/notebook/notebookInput';
|
||||
import { INotebookService, INotebookEditor, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { disposed } from 'vs/base/common/errors';
|
||||
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookChangeType, CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookChangeType, CellTypes } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { notebookModeId } from 'sql/common/constants';
|
||||
@@ -399,8 +399,8 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
||||
};
|
||||
let isUntitled: boolean = uri.scheme === Schemas.untitled;
|
||||
|
||||
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId) :
|
||||
this._editorService.createInput({ resource: uri, language: notebookModeId });
|
||||
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId, options.initialContent) :
|
||||
this._editorService.createInput({ resource: uri, language: notebookModeId });
|
||||
let input = this._instantiationService.createInstance(NotebookInput, path.basename(uri.fsPath), uri, fileInput);
|
||||
input.isTrusted = isUntitled;
|
||||
input.defaultKernel = options.defaultKernel;
|
||||
|
||||
@@ -854,6 +854,7 @@ export interface INotebookShowOptions {
|
||||
providerId?: string;
|
||||
connectionProfile?: azdata.IConnectionProfile;
|
||||
defaultKernel?: azdata.nb.IKernelSpec;
|
||||
initialContent?: string;
|
||||
}
|
||||
|
||||
export interface ExtHostNotebookDocumentsAndEditorsShape {
|
||||
|
||||
@@ -32,10 +32,8 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
||||
export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||
|
||||
private _root: HTMLElement;
|
||||
private _searchBox: InputBox;
|
||||
private _toDisposeViewlet: IDisposable[] = [];
|
||||
private _serverTreeView: ServerTreeView;
|
||||
private _clearSearchAction: ClearSearchAction;
|
||||
private _addServerAction: IAction;
|
||||
private _addServerGroupAction: IAction;
|
||||
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
||||
@@ -53,7 +51,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||
|
||||
super(VIEWLET_ID, configurationService, layoutService, telemetryService, _themeService, storageService);
|
||||
|
||||
this._clearSearchAction = this._instantiationService.createInstance(ClearSearchAction, ClearSearchAction.ID, ClearSearchAction.LABEL, this);
|
||||
this._addServerAction = this._instantiationService.createInstance(AddServerAction,
|
||||
AddServerAction.ID,
|
||||
AddServerAction.LABEL);
|
||||
@@ -79,24 +76,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||
super.create(parent);
|
||||
this._root = parent;
|
||||
const viewletContainer = DOM.append(parent, DOM.$('div.server-explorer-viewlet'));
|
||||
const searchBoxContainer = DOM.append(viewletContainer, DOM.$('div.search-box'));
|
||||
this._searchBox = new InputBox(
|
||||
searchBoxContainer,
|
||||
null,
|
||||
{
|
||||
placeholder: localize('Search server names', 'Search server names'),
|
||||
actions: [this._clearSearchAction],
|
||||
ariaLabel: localize('Search server names', 'Search server names')
|
||||
}
|
||||
);
|
||||
|
||||
this._searchBox.onDidChange(() => {
|
||||
this.search(this._searchBox.value);
|
||||
});
|
||||
|
||||
// Theme styler
|
||||
this._toDisposeViewlet.push(attachInputBoxStyler(this._searchBox, this._themeService));
|
||||
|
||||
const viewContainer = DOM.append(viewletContainer, DOM.$('div.object-explorer-view'));
|
||||
this._serverTreeView.renderBody(viewContainer).then(undefined, error => {
|
||||
warn('render registered servers: ' + error);
|
||||
@@ -105,7 +84,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||
|
||||
public search(value: string): void {
|
||||
if (value) {
|
||||
this._clearSearchAction.enabled = true;
|
||||
this._serverTreeView.searchTree(value);
|
||||
} else {
|
||||
this.clearSearch();
|
||||
@@ -129,8 +107,7 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||
}
|
||||
|
||||
public layout({ height, width }: DOM.Dimension): void {
|
||||
this._searchBox.layout();
|
||||
this._serverTreeView.layout(height - 36); // account for search box
|
||||
this._serverTreeView.layout(height);
|
||||
DOM.toggleClass(this._root, 'narrow', width <= 350);
|
||||
}
|
||||
|
||||
@@ -140,9 +117,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||
|
||||
public clearSearch() {
|
||||
this._serverTreeView.refreshTree();
|
||||
this._searchBox.value = '';
|
||||
this._clearSearchAction.enabled = false;
|
||||
this._searchBox.focus();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
.server-explorer-viewlet .object-explorer-view {
|
||||
height: calc(100% - 36px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.server-explorer-viewlet .server-group {
|
||||
|
||||
@@ -31,10 +31,8 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com
|
||||
export class ConnectionViewletPanel extends ViewletPanel {
|
||||
|
||||
private _root: HTMLElement;
|
||||
private _searchBox: InputBox;
|
||||
private _toDisposeViewlet: IDisposable[] = [];
|
||||
private _serverTreeView: ServerTreeView;
|
||||
private _clearSearchAction: ClearSearchAction;
|
||||
private _addServerAction: IAction;
|
||||
private _addServerGroupAction: IAction;
|
||||
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
||||
@@ -54,7 +52,7 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
|
||||
this._clearSearchAction = this.instantiationService.createInstance(ClearSearchAction, ClearSearchAction.ID, ClearSearchAction.LABEL, this);
|
||||
//this._clearSearchAction = this.instantiationService.createInstance(ClearSearchAction, ClearSearchAction.ID, ClearSearchAction.LABEL, this);
|
||||
this._addServerAction = this.instantiationService.createInstance(AddServerAction,
|
||||
AddServerAction.ID,
|
||||
AddServerAction.LABEL);
|
||||
@@ -77,24 +75,6 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
const viewletContainer = DOM.append(container, DOM.$('div.server-explorer-viewlet'));
|
||||
const searchBoxContainer = DOM.append(viewletContainer, DOM.$('div.search-box'));
|
||||
this._searchBox = new InputBox(
|
||||
searchBoxContainer,
|
||||
null,
|
||||
{
|
||||
placeholder: localize('Search server names', 'Search server names'),
|
||||
actions: [this._clearSearchAction],
|
||||
ariaLabel: localize('Search server names', 'Search server names')
|
||||
}
|
||||
);
|
||||
|
||||
this._searchBox.onDidChange(() => {
|
||||
this.search(this._searchBox.value);
|
||||
});
|
||||
|
||||
// Theme styler
|
||||
this._toDisposeViewlet.push(attachInputBoxStyler(this._searchBox, this.themeService));
|
||||
|
||||
const viewContainer = DOM.append(viewletContainer, DOM.$('div.object-explorer-view'));
|
||||
this._serverTreeView.renderBody(viewContainer).then(undefined, error => {
|
||||
console.warn('render registered servers: ' + error);
|
||||
@@ -103,8 +83,7 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
||||
}
|
||||
|
||||
layoutBody(size: number): void {
|
||||
this._searchBox.layout();
|
||||
this._serverTreeView.layout(size - 46); // account for search box and horizontal scroll bar
|
||||
this._serverTreeView.layout(size);
|
||||
DOM.toggleClass(this._root, 'narrow', this._root.clientWidth < 300);
|
||||
}
|
||||
|
||||
@@ -140,14 +119,10 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
||||
|
||||
public clearSearch() {
|
||||
this._serverTreeView.refreshTree();
|
||||
this._searchBox.value = '';
|
||||
this._clearSearchAction.enabled = false;
|
||||
this._searchBox.focus();
|
||||
}
|
||||
|
||||
public search(value: string): void {
|
||||
if (value) {
|
||||
this._clearSearchAction.enabled = true;
|
||||
this._serverTreeView.searchTree(value);
|
||||
} else {
|
||||
this.clearSearch();
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
|
||||
// Viewlet Action
|
||||
export class OpenDataExplorerViewletAction extends ShowViewletAction {
|
||||
public static ID = VIEWLET_ID;
|
||||
public static LABEL = localize('showDataExplorer', "Show Data Explorer");
|
||||
public static LABEL = localize('showDataExplorer', "Show Connections");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -39,7 +39,7 @@ export class OpenDataExplorerViewletAction extends ShowViewletAction {
|
||||
const viewletDescriptor = new ViewletDescriptor(
|
||||
DataExplorerViewlet,
|
||||
VIEWLET_ID,
|
||||
localize('workbench.dataExplorer', "Data Explorer"),
|
||||
localize('workbench.dataExplorer', "Connections"),
|
||||
'dataExplorer',
|
||||
0
|
||||
);
|
||||
|
||||
@@ -19,14 +19,9 @@ import { viewsContainersExtensionPoint } from 'vs/workbench/api/browser/viewsExt
|
||||
|
||||
import { CustomTreeView } from 'sql/workbench/browser/parts/views/customView';
|
||||
|
||||
export const DataExplorerViewlet = {
|
||||
DataExplorer: 'dataExplorer'
|
||||
};
|
||||
export const VIEWLET_ID = 'workbench.view.dataExplorer';
|
||||
export const VIEWLET_ID = 'workbench.view.connections';
|
||||
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
|
||||
|
||||
|
||||
|
||||
interface IUserFriendlyViewDescriptor {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -156,7 +151,7 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private showCollapsed(container: ViewContainer): boolean {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DISCONNECT_COMMAND_ID, MANAGE_COMMAND_ID, NEW_QUERY_COMMAND_ID, REFRESH
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
group: 'connection',
|
||||
order: 4,
|
||||
order: 3,
|
||||
command: {
|
||||
id: DISCONNECT_COMMAND_ID,
|
||||
title: localize('disconnect', 'Disconnect')
|
||||
@@ -20,6 +20,7 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
group: 'connection',
|
||||
order: 2,
|
||||
command: {
|
||||
id: NEW_QUERY_COMMAND_ID,
|
||||
title: localize('newQuery', 'New Query')
|
||||
@@ -29,7 +30,7 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
group: 'connection',
|
||||
order: 4,
|
||||
order: 1,
|
||||
command: {
|
||||
id: MANAGE_COMMAND_ID,
|
||||
title: localize('manage', 'Manage')
|
||||
|
||||
@@ -12,12 +12,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { CellContext, CellActionBase } from 'sql/parts/notebook/cellViews/codeActions';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { CellContext, CellActionBase } from 'sql/workbench/parts/notebook/cellViews/codeActions';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
|
||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { CellModel } from 'sql/parts/notebook/models/cell';
|
||||
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { CellModel } from 'sql/workbench/parts/notebook/models/cell';
|
||||
|
||||
export const HIDDEN_CLASS ='actionhidden';
|
||||
|
||||
@@ -31,8 +31,8 @@ export class CellToggleMoreActions {
|
||||
instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')),
|
||||
instantiationService.createInstance(AddCellFromContextAction,'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false),
|
||||
instantiationService.createInstance(AddCellFromContextAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true),
|
||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false),
|
||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true),
|
||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownBefore', localize('markdownBefore', 'Insert Text before'), CellTypes.Markdown, false),
|
||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', 'Insert Text after'), CellTypes.Markdown, true),
|
||||
instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', 'Clear output'))
|
||||
);
|
||||
}
|
||||
@@ -4,15 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./code';
|
||||
|
||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, Output, EventEmitter, OnChanges, SimpleChange, forwardRef, ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
|
||||
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
|
||||
import { ICellModel, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { CellToggleMoreActions } from 'sql/workbench/parts/notebook/cellToggleMoreActions';
|
||||
import { ICellModel, notebookConstants } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { RunCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { RunCellAction, CellContext } from 'sql/workbench/parts/notebook/cellViews/codeActions';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
@@ -28,9 +28,9 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
import { CellTypes } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import * as notebookUtils from 'sql/parts/notebook/notebookUtils';
|
||||
import * as notebookUtils from 'sql/workbench/parts/notebook/notebookUtils';
|
||||
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
|
||||
@@ -103,7 +103,8 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
||||
@Inject(IModelService) private _modelService: IModelService,
|
||||
@Inject(IModeService) private _modeService: IModeService,
|
||||
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
||||
@Inject(IConfigurationService) private _configurationService: IConfigurationService
|
||||
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef
|
||||
) {
|
||||
super();
|
||||
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
|
||||
@@ -161,7 +162,14 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
||||
&& this.cellModel.cellUri;
|
||||
}
|
||||
|
||||
private get destroyed(): boolean{
|
||||
return !!(this._changeRef['destroyed']);
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.createEditor();
|
||||
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
|
||||
this._layoutEmitter.fire();
|
||||
@@ -199,6 +207,14 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
||||
let overrideEditorSetting = this._configurationService.getValue<boolean>(OVERRIDE_EDITOR_THEMING_SETTING);
|
||||
this._editor.hideLineNumbers = (overrideEditorSetting && this.cellModel.cellType === CellTypes.Markdown);
|
||||
|
||||
|
||||
if (this.destroyed) {
|
||||
// At this point, we may have been disposed (scenario: restoring markdown cell in preview mode).
|
||||
// Exiting early to avoid warnings on registering already disposed items, which causes some churning
|
||||
// due to re-disposing things.
|
||||
// There's no negative impact as at this point the component isn't visible (it was removed from the DOM)
|
||||
return;
|
||||
}
|
||||
this._register(this._editor);
|
||||
this._register(this._editorInput);
|
||||
this._register(this._editorModel.onDidChangeContent(e => {
|
||||
@@ -11,11 +11,11 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
||||
import { ICellModel, CellExecutionState } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
import { getErrorMessage } from 'sql/workbench/parts/notebook/notebookUtils';
|
||||
import { ICellModel, CellExecutionState } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { MultiStateAction, IMultiStateData, IActionStateData } from 'sql/parts/notebook/notebookActions';
|
||||
import { MultiStateAction, IMultiStateData, IActionStateData } from 'sql/workbench/parts/notebook/notebookActions';
|
||||
|
||||
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
|
||||
const emptyExecutionCountLabel = '[ ]';
|
||||
@@ -9,7 +9,7 @@
|
||||
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
|
||||
</div>
|
||||
<div style="flex: 0 0 auto; width: 100%; height: 100%; display: block">
|
||||
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel">
|
||||
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel" [activeCellId]="activeCellId">
|
||||
</output-area-component>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges, AfterViewInit } from '@angular/core';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { CellView } from 'sql/workbench/parts/notebook/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
|
||||
|
||||
export const CODE_SELECTOR: string = 'code-cell-component';
|
||||
@@ -5,7 +5,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||
<div style="flex: 0 0 auto; user-select: initial;">
|
||||
<div #output ></div>
|
||||
<div style="flex: 0 0 auto; user-select: none;">
|
||||
<div #output class="output-userselect" ></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,19 +3,21 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./code';
|
||||
import 'vs/css!./media/output';
|
||||
|
||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild } from '@angular/core';
|
||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, SimpleChange } from '@angular/core';
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import { nb } from 'azdata';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import { MimeModel } from 'sql/parts/notebook/outputs/common/mimemodel';
|
||||
import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor';
|
||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||
import 'vs/css!sql/parts/notebook/outputs/style/index';
|
||||
import { MimeModel } from 'sql/workbench/parts/notebook/outputs/common/mimemodel';
|
||||
import * as outputProcessor from 'sql/workbench/parts/notebook/outputs/common/outputProcessor';
|
||||
import { RenderMimeRegistry } from 'sql/workbench/parts/notebook/outputs/registry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
|
||||
export const OUTPUT_SELECTOR: string = 'output-component';
|
||||
const USER_SELECT_CLASS ='actionselect';
|
||||
|
||||
@Component({
|
||||
selector: OUTPUT_SELECTOR,
|
||||
@@ -28,6 +30,7 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
||||
private _trusted: boolean;
|
||||
private _initialized: boolean = false;
|
||||
private readonly _minimumHeight = 30;
|
||||
private _activeCellId: string;
|
||||
registry: RenderMimeRegistry;
|
||||
|
||||
|
||||
@@ -47,6 +50,27 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
|
||||
for (let propName in changes) {
|
||||
if (propName === 'activeCellId') {
|
||||
this.toggleUserSelect(this.isActive());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private toggleUserSelect(userSelect: boolean): void {
|
||||
if (!this.outputElement) {
|
||||
return;
|
||||
}
|
||||
if (userSelect) {
|
||||
DOM.addClass(this.outputElement.nativeElement, USER_SELECT_CLASS);
|
||||
} else {
|
||||
DOM.removeClass(this.outputElement.nativeElement, USER_SELECT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
private renderOutput() {
|
||||
let node = this.outputElement.nativeElement;
|
||||
let output = this.cellOutput;
|
||||
@@ -63,14 +87,21 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
||||
return this._trusted;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set trustedMode(value: boolean) {
|
||||
@Input() set trustedMode(value: boolean) {
|
||||
this._trusted = value;
|
||||
if (this._initialized) {
|
||||
this.renderOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@Input() set activeCellId(value: string) {
|
||||
this._activeCellId = value;
|
||||
}
|
||||
|
||||
get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
protected createRenderedMimetype(options: MimeModel.IOptions, node: HTMLElement): void {
|
||||
let mimeType = this.registry.preferredMimeType(
|
||||
options.data,
|
||||
@@ -100,4 +131,7 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
||||
//this.setState({ node: node });
|
||||
}
|
||||
}
|
||||
protected isActive() {
|
||||
return this.cellModel && this.cellModel.id === this.activeCellId;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
-->
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||
<div #outputarea class="notebook-output" style="flex: 0 0 auto;">
|
||||
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" [cellModel]="cellModel">
|
||||
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" [cellModel]="cellModel" [activeCellId]="activeCellId">
|
||||
</output-component>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,7 +6,7 @@ import 'vs/css!./code';
|
||||
import 'vs/css!./outputArea';
|
||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef } from '@angular/core';
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
|
||||
@@ -20,6 +20,8 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
|
||||
@ViewChild('outputarea', { read: ElementRef }) private outputArea: ElementRef;
|
||||
@Input() cellModel: ICellModel;
|
||||
|
||||
private _activeCellId: string;
|
||||
|
||||
private readonly _minimumHeight = 30;
|
||||
|
||||
constructor(
|
||||
@@ -41,6 +43,14 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
@Input() set activeCellId(value: string) {
|
||||
this._activeCellId = value;
|
||||
}
|
||||
|
||||
get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
private updateTheme(theme: IColorTheme): void {
|
||||
let outputElement = <HTMLElement>this.outputArea.nativeElement;
|
||||
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||
@@ -10,4 +10,13 @@ output-area-component .notebook-output {
|
||||
border-top-width: 0px;
|
||||
user-select: text;
|
||||
padding: 5px 20px 0px;
|
||||
}
|
||||
|
||||
.output-userselect.actionselect {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.output-userselect pre{
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
import 'vs/css!./placeholder';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { CellView } from 'sql/workbench/parts/notebook/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
|
||||
|
||||
export const PLACEHOLDER_SELECTOR: string = 'placeholder-cell-component';
|
||||
@@ -11,7 +11,7 @@
|
||||
</code-component>
|
||||
</div>
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
||||
<div #preview class ="notebook-preview" style="flex: 1 1 auto; user-select: initial;" (dblclick)="toggleEditMode()">
|
||||
<div #preview class ="notebook-preview" style="flex: 1 1 auto" (dblclick)="toggleEditMode()">
|
||||
</div>
|
||||
<div #moreactions class="moreActions" style="flex: 0 0 auto; display: flex; flex-flow:column;width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
|
||||
</div>
|
||||
@@ -5,6 +5,7 @@
|
||||
import 'vs/css!./textCell';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, OnChanges, SimpleChange } from '@angular/core';
|
||||
import * as path from 'path';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
@@ -14,15 +15,17 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ISanitizer, defaultSanitizer } from 'sql/parts/notebook/outputs/sanitizer';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
|
||||
import { CellView } from 'sql/workbench/parts/notebook/cellViews/interfaces';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { ISanitizer, defaultSanitizer } from 'sql/workbench/parts/notebook/outputs/sanitizer';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
import { CellToggleMoreActions } from 'sql/workbench/parts/notebook/cellToggleMoreActions';
|
||||
|
||||
export const TEXT_SELECTOR: string = 'text-cell-component';
|
||||
const USER_SELECT_CLASS ='actionselect';
|
||||
|
||||
@Component({
|
||||
selector: TEXT_SELECTOR,
|
||||
@@ -111,6 +114,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
if (propName === 'activeCellId') {
|
||||
let changedProp = changes[propName];
|
||||
this._activeCellId = changedProp.currentValue;
|
||||
this.toggleUserSelect(this.isActive());
|
||||
// If the activeCellId is undefined (i.e. in an active cell update), don't unnecessarily set editMode to false;
|
||||
// it will be set to true in a subsequent call to toggleEditMode()
|
||||
if (changedProp.previousValue !== undefined) {
|
||||
@@ -133,8 +137,9 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
} else {
|
||||
this._content = this.sanitizeContent(this.cellModel.source);
|
||||
}
|
||||
// todo: pass in the notebook filename instead of undefined value
|
||||
this._commandService.executeCommand<string>('notebook.showPreview', undefined, this._content).then((htmlcontent) => {
|
||||
|
||||
this._commandService.executeCommand<string>('notebook.showPreview', this.cellModel.notebookModel.notebookUri, this._content).then((htmlcontent) => {
|
||||
htmlcontent = this.convertVscodeResourceToFileInSubDirectories(htmlcontent);
|
||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||
outputElement.innerHTML = htmlcontent;
|
||||
});
|
||||
@@ -149,6 +154,24 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Only replace vscode-resource with file when in the same (or a sub) directory
|
||||
// This matches Jupyter Notebook viewer behavior
|
||||
private convertVscodeResourceToFileInSubDirectories(htmlContent: string): string {
|
||||
let htmlContentCopy = htmlContent;
|
||||
while (htmlContentCopy.search('(?<=img src=\"vscode-resource:)') > 0) {
|
||||
let pathStartIndex = htmlContentCopy.search('(?<=img src=\"vscode-resource:)');
|
||||
let pathEndIndex = htmlContentCopy.indexOf('\" ', pathStartIndex);
|
||||
let filePath = htmlContentCopy.substring(pathStartIndex, pathEndIndex);
|
||||
// If the asset is in the same folder or a subfolder, replace 'vscode-resource:' with 'file:', so the image is visible
|
||||
if (!path.relative(path.dirname(this.cellModel.notebookModel.notebookUri.fsPath), filePath).includes('..')) {
|
||||
// ok to change from vscode-resource: to file:
|
||||
htmlContent = htmlContent.replace('vscode-resource:'+ filePath, 'file:' + filePath);
|
||||
}
|
||||
htmlContentCopy = htmlContentCopy.slice(pathEndIndex);
|
||||
}
|
||||
return htmlContent;
|
||||
}
|
||||
|
||||
|
||||
// Todo: implement layout
|
||||
public layout() {
|
||||
@@ -182,6 +205,17 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
private toggleUserSelect(userSelect: boolean): void {
|
||||
if (!this.output) {
|
||||
return;
|
||||
}
|
||||
if (userSelect) {
|
||||
DOM.addClass(this.output.nativeElement, USER_SELECT_CLASS);
|
||||
} else {
|
||||
DOM.removeClass(this.output.nativeElement, USER_SELECT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
private setFocusAndScroll(): void {
|
||||
this.toggleEditMode(this.isActive());
|
||||
|
||||
@@ -8,7 +8,11 @@ text-cell-component {
|
||||
}
|
||||
|
||||
text-cell-component .notebook-preview {
|
||||
user-select: initial;
|
||||
user-select: none;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.notebook-preview.actionselect {
|
||||
user-select: text;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 285 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 549 B After Width: | Height: | Size: 549 B |
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 774 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 818 B After Width: | Height: | Size: 818 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 261 B |
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -13,9 +13,9 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { CellTypes, CellType, NotebookChangeType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { ICellModelOptions, IModelFactory, FutureInternal, CellExecutionState } from './modelInterfaces';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
@@ -210,6 +210,10 @@ export class CellModel implements ICellModel {
|
||||
await kernel.interrupt();
|
||||
} else {
|
||||
// TODO update source based on editor component contents
|
||||
if (kernel.requiresConnection && !this.notebookModel.activeConnection) {
|
||||
this.sendNotification(notificationService, Severity.Error, localize('kernelRequiresConnection', "Please select a connection to run cells for this kernel"));
|
||||
return false;
|
||||
}
|
||||
let content = this.source;
|
||||
if (content) {
|
||||
let future = await kernel.requestExecute({
|
||||
@@ -386,16 +390,8 @@ export class CellModel implements ICellModel {
|
||||
let endpoint = this.getKnoxEndpoint(model.activeConnection);
|
||||
let host = endpoint && endpoint.ipAddress ? endpoint.ipAddress : model.activeConnection.serverName;
|
||||
let html = result.data['text/html'];
|
||||
html = html.replace(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, function (a, b, c) {
|
||||
let ret = '';
|
||||
if (b !== '') {
|
||||
ret = 'https://' + host + ':30443/gateway/default/yarn/proxy';
|
||||
}
|
||||
if (c !== '') {
|
||||
ret = ret + c;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
html =this.rewriteUrlUsingRegex(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, html, host);
|
||||
html =this.rewriteUrlUsingRegex(/(https?:\/\/master.*master-svc.*\/proxy)(.*)/g, html, host);
|
||||
(<nb.IDisplayResult>output).data['text/html'] = html;
|
||||
}
|
||||
}
|
||||
@@ -405,6 +401,19 @@ export class CellModel implements ICellModel {
|
||||
return output;
|
||||
}
|
||||
|
||||
private rewriteUrlUsingRegex(regex: RegExp, html: string, host: string): string {
|
||||
return html.replace(regex, function (a, b, c) {
|
||||
let ret = '';
|
||||
if (b !== '') {
|
||||
ret = 'https://' + host + ':30443/gateway/default/yarn/proxy';
|
||||
}
|
||||
if (c !== '') {
|
||||
ret = ret + c;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
private getDisplayId(msg: nb.IIOPubMessage): string | undefined {
|
||||
let transient = (msg.content.transient || {});
|
||||
return transient['display_id'] as string;
|
||||
@@ -420,7 +429,7 @@ export class CellModel implements ICellModel {
|
||||
if (this._cellType === CellTypes.Code) {
|
||||
cellJson.metadata.language = this._language,
|
||||
cellJson.outputs = this._outputs;
|
||||
cellJson.execution_count = this.executionCount;
|
||||
cellJson.execution_count = this.executionCount ? this.executionCount : 0;
|
||||
}
|
||||
return cellJson as nb.ICellContents;
|
||||
}
|
||||
@@ -481,7 +490,10 @@ export class CellModel implements ICellModel {
|
||||
if (serverInfo && serverInfo.options && serverInfo.options['clusterEndpoints']) {
|
||||
let endpoints: notebookUtils.IEndpoint[] = serverInfo.options['clusterEndpoints'];
|
||||
if (endpoints && endpoints.length > 0) {
|
||||
endpoint = endpoints.find(ep => ep.serviceName.toLowerCase() === 'knox');
|
||||
endpoint = endpoints.find(ep => {
|
||||
let serviceName: string = ep.serviceName.toLowerCase();
|
||||
return serviceName === 'knox' || serviceName === 'gateway';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ICellMagicMapper, ILanguageMagic } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ICellMagicMapper, ILanguageMagic } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
|
||||
const defaultKernel = '*';
|
||||
export class CellMagicMapper implements ICellMagicMapper {
|
||||
@@ -14,16 +14,16 @@ 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/parts/notebook/models/contracts';
|
||||
import { CellType, NotebookChangeType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { INotebookManager } from 'sql/workbench/services/notebook/common/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/parts/notebook/notebookUtils';
|
||||
import { IStandardKernelWithProvider } from 'sql/workbench/parts/notebook/notebookUtils';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
|
||||
export interface IClientSessionOptions {
|
||||
notebookUri: URI;
|
||||
@@ -8,7 +8,7 @@
|
||||
import { nb } from 'azdata';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDefaultConnection, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { IDefaultConnection, notebookConstants } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
@@ -12,11 +12,11 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, NotebookContentChange, notebookConstants } from './modelInterfaces';
|
||||
import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { NotebookChangeType, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { nbversion } from '../notebookConstants';
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import { NotebookContexts } from 'sql/parts/notebook/models/notebookContexts';
|
||||
import { NotebookContexts } from 'sql/workbench/parts/notebook/models/notebookContexts';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -22,26 +22,26 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||
import * as notebookUtils from 'sql/parts/notebook/notebookUtils';
|
||||
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||
import { ModelFactory } from 'sql/workbench/parts/notebook/models/modelFactory';
|
||||
import * as notebookUtils from 'sql/workbench/parts/notebook/notebookUtils';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, RunAllCellsAction, ClearAllOutputsAction } from 'sql/parts/notebook/notebookActions';
|
||||
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, RunAllCellsAction, ClearAllOutputsAction } from 'sql/workbench/parts/notebook/notebookActions';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { CellMagicMapper } from 'sql/parts/notebook/models/cellMagicMapper';
|
||||
import { CellMagicMapper } from 'sql/workbench/parts/notebook/models/cellMagicMapper';
|
||||
import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { CellModel } from 'sql/parts/notebook/models/cell';
|
||||
import { CellModel } from 'sql/workbench/parts/notebook/models/cell';
|
||||
|
||||
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
|
||||
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
|
||||
import { NotebookInput } from 'sql/workbench/parts/notebook/notebookInput';
|
||||
import { NotebookEditor } from 'sql/workbench/parts/notebook/notebookEditor';
|
||||
|
||||
// Model View editor registration
|
||||
const viewModelEditorDescriptor = new EditorDescriptor(
|
||||