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/parts/jobManagement/common/media/*.svg',
|
||||||
'out-build/sql/media/objectTypes/*.svg',
|
'out-build/sql/media/objectTypes/*.svg',
|
||||||
'out-build/sql/media/icons/*.svg',
|
'out-build/sql/media/icons/*.svg',
|
||||||
'out-build/sql/parts/notebook/media/**/*.svg',
|
'out-build/sql/workbench/parts/notebook/media/**/*.svg',
|
||||||
'!**/test/**'
|
'!**/test/**'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -134,8 +134,10 @@ export class MarkdownEngine {
|
|||||||
|
|
||||||
// {{SQL CARBON EDIT}} - Add renderText method
|
// {{SQL CARBON EDIT}} - Add renderText method
|
||||||
public async renderText(document: vscode.Uri, text: string): Promise<string> {
|
public async renderText(document: vscode.Uri, text: string): Promise<string> {
|
||||||
const engine = await this.getEngine(this.getConfig(document));
|
const config = this.getConfig(document);
|
||||||
return engine.render(text);
|
const engine = await this.getEngine(config);
|
||||||
|
this.currentDocument = document;
|
||||||
|
return engine.render(text, config);
|
||||||
}
|
}
|
||||||
// {{SQL CARBON EDIT}} - End
|
// {{SQL CARBON EDIT}} - End
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,14 @@
|
|||||||
{
|
{
|
||||||
"command": "mssqlCluster.livy.cmd.submitFileToSparkJob",
|
"command": "mssqlCluster.livy.cmd.submitFileToSparkJob",
|
||||||
"title": "%title.submitSparkJob%"
|
"title": "%title.submitSparkJob%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mssql.searchServers",
|
||||||
|
"title": "%title.searchServers%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mssql.clearSearchServerResult",
|
||||||
|
"title": "%title.clearSearchServerResult%"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputChannels": [
|
"outputChannels": [
|
||||||
|
|||||||
@@ -24,5 +24,8 @@
|
|||||||
"title.openYarnHistory": "View Yarn History",
|
"title.openYarnHistory": "View Yarn History",
|
||||||
"title.tasks": "Tasks",
|
"title.tasks": "Tasks",
|
||||||
"title.installPackages": "Install Packages",
|
"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#}",
|
"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": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export const extensionConfigSectionName = 'mssql';
|
|||||||
|
|
||||||
// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
|
// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
|
||||||
export const mssqlClusterProviderName = 'mssqlCluster';
|
export const mssqlClusterProviderName = 'mssqlCluster';
|
||||||
export const hadoopKnoxEndpointName = 'Knox';
|
export const hadoopEndpointNameKnox = 'Knox';
|
||||||
|
export const hadoopEndpointNameGateway = 'gateway';
|
||||||
export const protocolVersion = '1.0';
|
export const protocolVersion = '1.0';
|
||||||
export const hostPropName = 'host';
|
export const hostPropName = 'host';
|
||||||
export const userPropName = 'user';
|
export const userPropName = 'user';
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { OpenSparkJobSubmissionDialogCommand, OpenSparkJobSubmissionDialogFromFi
|
|||||||
import { OpenSparkYarnHistoryTask } from './sparkFeature/historyTask';
|
import { OpenSparkYarnHistoryTask } from './sparkFeature/historyTask';
|
||||||
import { MssqlObjectExplorerNodeProvider, mssqlOutputChannel } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
|
import { MssqlObjectExplorerNodeProvider, mssqlOutputChannel } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
|
||||||
import { CmsService } from './cms/cmsService';
|
import { CmsService } from './cms/cmsService';
|
||||||
|
import { registerSearchServerCommand } from './objectExplorerNodeProvider/command';
|
||||||
|
|
||||||
const baseConfig = require('./config.json');
|
const baseConfig = require('./config.json');
|
||||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
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');
|
vscode.window.showErrorMessage('Failed to start Sql tools service');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerSearchServerCommand(appContext);
|
||||||
let contextProvider = new ContextProvider();
|
let contextProvider = new ContextProvider();
|
||||||
context.subscriptions.push(contextProvider);
|
context.subscriptions.push(contextProvider);
|
||||||
context.subscriptions.push(credentialsStore);
|
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(
|
await this.executeWithProgress(
|
||||||
async (cancelToken: vscode.CancellationTokenSource) => {
|
async (cancelToken: vscode.CancellationTokenSource) => {
|
||||||
let contents = await fileNode.getFileContentsAsString(PreviewFileCommand.DefaultMaxSize);
|
let contents = await fileNode.getFileContentsAsString(PreviewFileCommand.DefaultMaxSize);
|
||||||
let doc = await this.openTextDocument(fspath.basename(fileNode.hdfsPath));
|
let fileName: string = fspath.basename(fileNode.hdfsPath);
|
||||||
let editor = await this.apiWrapper.showTextDocument(doc, vscode.ViewColumn.Active, false);
|
if (fspath.extname(fileName) !== '.ipynb') {
|
||||||
await editor.edit(edit => {
|
let doc = await this.openTextDocument(fileName);
|
||||||
edit.insert(new vscode.Position(0, 0), contents);
|
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'),
|
localize('previewing', 'Generating preview'),
|
||||||
false);
|
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> {
|
private async openTextDocument(fileName: string): Promise<vscode.TextDocument> {
|
||||||
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true);
|
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true);
|
||||||
if (docUri) {
|
if (docUri) {
|
||||||
|
|||||||
@@ -79,7 +79,11 @@ async function createSqlClusterConnInfo(sqlConnInfo: azdata.IConnectionProfile |
|
|||||||
let endpoints: IEndpoint[] = serverInfo.options[constants.clusterEndpointsProperty];
|
let endpoints: IEndpoint[] = serverInfo.options[constants.clusterEndpointsProperty];
|
||||||
if (!endpoints || endpoints.length === 0) { return undefined; }
|
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; }
|
if (index < 0) { return undefined; }
|
||||||
|
|
||||||
let credentials = await azdata.connection.getCredentials(connectionId);
|
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
|
// Don't wait on installation, since there's currently no Cancel functionality
|
||||||
this.jupyterInstallation.startInstallProcess(pythonLocation)
|
this.jupyterInstallation.startInstallProcess(false, pythonLocation)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._setupComplete.resolve();
|
this._setupComplete.resolve();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import * as nls from 'vscode-nls';
|
|||||||
import { JupyterController } from './jupyter/jupyterController';
|
import { JupyterController } from './jupyter/jupyterController';
|
||||||
import { AppContext } from './common/appContext';
|
import { AppContext } from './common/appContext';
|
||||||
import { ApiWrapper } from './common/apiWrapper';
|
import { ApiWrapper } from './common/apiWrapper';
|
||||||
|
import { IExtensionApi } from './types';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
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 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');
|
const noNotebookVisible = localize('noNotebookVisible', 'No notebook editor is active');
|
||||||
|
|
||||||
|
let controller: JupyterController;
|
||||||
|
|
||||||
export let controller: JupyterController;
|
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
||||||
|
|
||||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
|
||||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', (context?: azdata.ConnectedContext) => {
|
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', (context?: azdata.ConnectedContext) => {
|
||||||
let connectionProfile: azdata.IConnectionProfile = undefined;
|
let connectionProfile: azdata.IConnectionProfile = undefined;
|
||||||
if (context && context.connectionProfile) {
|
if (context && context.connectionProfile) {
|
||||||
@@ -49,7 +49,16 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
let appContext = new AppContext(extensionContext, new ApiWrapper());
|
let appContext = new AppContext(extensionContext, new ApiWrapper());
|
||||||
controller = new JupyterController(appContext);
|
controller = new JupyterController(appContext);
|
||||||
controller.activate();
|
let result = await controller.activate();
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getJupyterController() {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function newNotebook(connectionProfile: azdata.IConnectionProfile) {
|
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!
|
// Note: this will go forever if it's coded wrong, or you have infinite Untitled notebooks!
|
||||||
while (true) {
|
while (true) {
|
||||||
let title = `Notebook-${nextVal}`;
|
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;
|
let hasNotebookDoc = azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
|
||||||
if (!hasNotebookDoc) {
|
if (!hasTextDoc && !hasNotebookDoc) {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
nextVal++;
|
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';
|
'use strict';
|
||||||
|
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as assert from 'assert';
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as tempWrite from 'temp-write';
|
import * as tempWrite from 'temp-write';
|
||||||
|
import * as assert from 'assert';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { JupyterController } from '../jupyter/jupyterController';
|
import { JupyterController } from '../jupyter/jupyterController';
|
||||||
import { INotebook, CellTypes } from '../contracts/content';
|
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);
|
this.timeout(600000);
|
||||||
|
|
||||||
let expectedNotebookContent: INotebook = {
|
let expectedNotebookContent: INotebook = {
|
||||||
@@ -35,12 +36,34 @@ describe('Notebook Integration Test', function (): void {
|
|||||||
nbformat_minor: 2
|
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 () {
|
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 pythonNotebook = Object.assign({}, expectedNotebookContent, { metadata: { kernelspec: { name: 'python3', display_name: 'Python 3' } } });
|
||||||
let uri = writeNotebookToFile(pythonNotebook);
|
let uri = writeNotebookToFile(pythonNotebook);
|
||||||
await ensureJupyterInstalled();
|
|
||||||
|
|
||||||
let notebook = await azdata.nb.showNotebookDocument(uri);
|
let notebook = await azdata.nb.showNotebookDocument(uri);
|
||||||
should(notebook.document.cells).have.length(1);
|
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'];
|
let result = (<azdata.nb.IExecuteResult>cellOutputs[0]).data['text/plain'];
|
||||||
should(result).equal('2');
|
should(result).equal('2');
|
||||||
|
|
||||||
try {
|
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
||||||
// 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) { }
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {
|
function writeNotebookToFile(pythonNotebook: INotebook): vscode.Uri {
|
||||||
let notebookContentString = JSON.stringify(pythonNotebook);
|
let notebookContentString = JSON.stringify(pythonNotebook);
|
||||||
let localFile = tempWrite.sync(notebookContentString, 'notebook.ipynb');
|
let localFile = tempWrite.sync(notebookContentString, 'notebook.ipynb');
|
||||||
let uri = vscode.Uri.file(localFile);
|
let uri = vscode.Uri.file(localFile);
|
||||||
return uri;
|
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> {
|
private async handleDependenciesReinstallation(): Promise<void> {
|
||||||
if (await this.confirmReinstall()) {
|
try {
|
||||||
this._jupyterInstallation = await JupyterServerInstallation.getInstallation(
|
let doReinstall = await this.confirmReinstall();
|
||||||
this.extensionContext.extensionPath,
|
if (doReinstall) {
|
||||||
this.outputChannel,
|
await this._jupyterInstallation.startInstallProcess(true);
|
||||||
this.apiWrapper,
|
}
|
||||||
undefined,
|
} catch (err) {
|
||||||
true);
|
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ export class JupyterKernel implements nb.IKernel {
|
|||||||
return true;
|
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 {
|
public get isReady(): boolean {
|
||||||
return this.kernelImpl.isReady;
|
return this.kernelImpl.isReady;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const msgPythonUnpackError = localize('msgPythonUnpackError', 'Error while unpac
|
|||||||
const msgTaskName = localize('msgTaskName', 'Installing Notebook dependencies');
|
const msgTaskName = localize('msgTaskName', 'Installing Notebook dependencies');
|
||||||
const msgInstallPkgStart = localize('msgInstallPkgStart', 'Installing Notebook dependencies, see Tasks view for more information');
|
const msgInstallPkgStart = localize('msgInstallPkgStart', 'Installing Notebook dependencies, see Tasks view for more information');
|
||||||
const msgInstallPkgFinish = localize('msgInstallPkgFinish', 'Notebook dependencies installation is complete');
|
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 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); }
|
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
|
// Allows dependencies to be installed even if an existing installation is already present
|
||||||
private _forceInstall: boolean;
|
private _forceInstall: boolean;
|
||||||
|
private _installInProgress: boolean;
|
||||||
|
|
||||||
private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
|
private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
|
||||||
|
|
||||||
private _installReady: Deferred<void>;
|
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
||||||
|
|
||||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string, forceInstall?: boolean) {
|
|
||||||
this.extensionPath = extensionPath;
|
this.extensionPath = extensionPath;
|
||||||
this.outputChannel = outputChannel;
|
this.outputChannel = outputChannel;
|
||||||
this.apiWrapper = apiWrapper;
|
this.apiWrapper = apiWrapper;
|
||||||
this._pythonInstallationPath = pythonInstallationPath || JupyterServerInstallation.getPythonInstallPath(this.apiWrapper);
|
this._pythonInstallationPath = pythonInstallationPath || JupyterServerInstallation.getPythonInstallPath(this.apiWrapper);
|
||||||
this._forceInstall = !!forceInstall;
|
this._forceInstall = false;
|
||||||
|
this._installInProgress = false;
|
||||||
|
|
||||||
this.configurePackagePaths();
|
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> {
|
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.
|
// Update python paths and properties to reference user's local python.
|
||||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
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);
|
this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix);
|
||||||
|
|
||||||
// Store paths to python libraries required to run jupyter.
|
// Store paths to python libraries required to run jupyter.
|
||||||
@@ -230,6 +210,11 @@ export default class JupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
this.pythonEnvVarPath = this.pythonBinPath + delimiter + this.pythonEnvVarPath;
|
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.
|
// Store the executable options to run child processes with env var without interfering parent env var.
|
||||||
let env = Object.assign({}, process.env);
|
let env = Object.assign({}, process.env);
|
||||||
delete env['Path']; // Delete extra 'Path' variable for Windows, just in case.
|
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> {
|
private isPythonRunning(pythonInstallPath: string): Promise<boolean> {
|
||||||
if (pythonInstallationPath) {
|
let pythonExePath = JupyterServerInstallation.getPythonExePath(pythonInstallPath);
|
||||||
this._pythonInstallationPath = pythonInstallationPath;
|
return new Promise<boolean>(resolve => {
|
||||||
this.configurePackagePaths();
|
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);
|
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) {
|
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall) {
|
||||||
this.apiWrapper.startBackgroundOperation({
|
this.apiWrapper.startBackgroundOperation({
|
||||||
displayName: msgTaskName,
|
displayName: msgTaskName,
|
||||||
@@ -255,27 +276,32 @@ export default class JupyterServerInstallation {
|
|||||||
isCancelable: false,
|
isCancelable: false,
|
||||||
operation: op => {
|
operation: op => {
|
||||||
this.installDependencies(op)
|
this.installDependencies(op)
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
this._installReady.resolve();
|
await updateConfig();
|
||||||
updateConfig();
|
installReady.resolve();
|
||||||
|
this._installInProgress = false;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
|
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
|
||||||
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
|
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
|
||||||
this.apiWrapper.showErrorMessage(errorMsg);
|
this.apiWrapper.showErrorMessage(errorMsg);
|
||||||
this._installReady.reject(errorMsg);
|
installReady.reject(errorMsg);
|
||||||
|
this._installInProgress = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Python executable already exists, but the path setting wasn't defined,
|
// Python executable already exists, but the path setting wasn't defined,
|
||||||
// so update it here
|
// so update it here
|
||||||
this._installReady.resolve();
|
await updateConfig();
|
||||||
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> {
|
public async promptForPythonInstall(): Promise<void> {
|
||||||
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
||||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, this);
|
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, this);
|
||||||
@@ -312,6 +338,10 @@ export default class JupyterServerInstallation {
|
|||||||
return this._pythonExecutable;
|
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 {
|
public static isPythonInstalled(apiWrapper: ApiWrapper): boolean {
|
||||||
// Don't use _pythonExecutable here, since it could be populated with a default value
|
// Don't use _pythonExecutable here, since it could be populated with a default value
|
||||||
let pathSetting = JupyterServerInstallation.getPythonPathSetting(apiWrapper);
|
let pathSetting = JupyterServerInstallation.getPythonPathSetting(apiWrapper);
|
||||||
@@ -319,13 +349,15 @@ export default class JupyterServerInstallation {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pythonExe = path.join(
|
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting);
|
||||||
pathSetting,
|
|
||||||
constants.pythonBundleVersion,
|
|
||||||
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
|
||||||
return fs.existsSync(pythonExe);
|
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 {
|
public static getPythonInstallPath(apiWrapper: ApiWrapper): string {
|
||||||
let userPath = JupyterServerInstallation.getPythonPathSetting(apiWrapper);
|
let userPath = JupyterServerInstallation.getPythonPathSetting(apiWrapper);
|
||||||
return userPath ? userPath : JupyterServerInstallation.DefaultPythonLocation;
|
return userPath ? userPath : JupyterServerInstallation.DefaultPythonLocation;
|
||||||
@@ -345,6 +377,11 @@ export default class JupyterServerInstallation {
|
|||||||
return path;
|
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 {
|
public static getPythonBinPath(apiWrapper: ApiWrapper): string {
|
||||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||||
|
|
||||||
@@ -353,4 +390,11 @@ export default class JupyterServerInstallation {
|
|||||||
constants.pythonBundleVersion,
|
constants.pythonBundleVersion,
|
||||||
pythonBinPathSuffix);
|
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_SERVER = 'host';
|
||||||
const KNOX_ENDPOINT_PORT = 'knoxport';
|
const KNOX_ENDPOINT_PORT = 'knoxport';
|
||||||
const KNOX_ENDPOINT = 'knox';
|
const KNOX_ENDPOINT_KNOX = 'knox';
|
||||||
|
const KNOX_ENDPOINT_GATEWAY = 'gateway';
|
||||||
const SQL_PROVIDER = 'MSSQL';
|
const SQL_PROVIDER = 'MSSQL';
|
||||||
const USER = 'user';
|
const USER = 'user';
|
||||||
const DEFAULT_CLUSTER_USER_NAME = 'root';
|
const DEFAULT_CLUSTER_USER_NAME = 'root';
|
||||||
@@ -242,7 +243,9 @@ export class JupyterSession implements nb.ISession {
|
|||||||
|
|
||||||
//Update server info with bigdata endpoint - Unified Connection
|
//Update server info with bigdata endpoint - Unified Connection
|
||||||
if (connection.providerName === SQL_PROVIDER) {
|
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) {
|
if (!clusterEndpoint) {
|
||||||
let kernelDisplayName: string = await this.getKernelDisplayName();
|
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.')));
|
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.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* 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 path = require('path');
|
||||||
const testRunner = require('vscode/lib/testrunner');
|
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",
|
"version": "0.0.1",
|
||||||
"description": "Dependencies shared by all extensions",
|
"description": "Dependencies shared by all extensions",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"typescript": "3.4.1"
|
"typescript": "3.4.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node ./postinstall"
|
"postinstall": "node ./postinstall"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
typescript@3.4.1:
|
typescript@3.4.3:
|
||||||
version "3.4.1"
|
version "3.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
|
||||||
integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
|
integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ pushd %~dp0\..
|
|||||||
|
|
||||||
set VSCODEUSERDATADIR=%TMP%\adsuser-%RANDOM%-%TIME:~6,5%
|
set VSCODEUSERDATADIR=%TMP%\adsuser-%RANDOM%-%TIME:~6,5%
|
||||||
set VSCODEEXTENSIONSDIR=%TMP%\adsext-%RANDOM%-%TIME:~6,5%
|
set VSCODEEXTENSIONSDIR=%TMP%\adsext-%RANDOM%-%TIME:~6,5%
|
||||||
|
set PYTHON_TEST_PATH=%VSCODEUSERDATADIR%\TestPythonInstallation
|
||||||
echo %VSCODEUSERDATADIR%
|
echo %VSCODEUSERDATADIR%
|
||||||
echo %VSCODEEXTENSIONSDIR%
|
echo %VSCODEEXTENSIONSDIR%
|
||||||
|
echo %PYTHON_TEST_PATH%
|
||||||
@echo OFF
|
@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
|
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%
|
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||||
|
|||||||
@@ -12,12 +12,15 @@ else
|
|||||||
VSCODEEXTDIR=`mktemp -d 2>/dev/null`
|
VSCODEEXTDIR=`mktemp -d 2>/dev/null`
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export PYTHON_TEST_PATH=$VSCODEUSERDATADIR/TestPythonInstallation
|
||||||
|
|
||||||
cd $ROOT
|
cd $ROOT
|
||||||
echo $VSCODEUSERDATADIR
|
echo $VSCODEUSERDATADIR
|
||||||
echo $VSCODEEXTDIR
|
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
|
./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 -f $VSCODEUSERDATADIR
|
||||||
rm -r $VSCODEUSERDATADIR
|
|
||||||
rm -r $VSCODEEXTDIR
|
rm -r $VSCODEEXTDIR
|
||||||
|
|||||||
6
src/sql/azdata.proposed.d.ts
vendored
@@ -4085,6 +4085,11 @@ declare module 'azdata' {
|
|||||||
* Default kernel for notebook
|
* Default kernel for notebook
|
||||||
*/
|
*/
|
||||||
defaultKernel?: nb.IKernelSpec;
|
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 id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly supportsIntellisense: boolean;
|
readonly supportsIntellisense: boolean;
|
||||||
|
readonly requiresConnection?: boolean;
|
||||||
/**
|
/**
|
||||||
* Test whether the kernel is ready.
|
* Test whether the kernel is ready.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
.monaco-table * {
|
.monaco-table * {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-table {
|
.monaco-table {
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
|
|
||||||
.list-row.account-picker-list .label .contextual-display-name {
|
.list-row.account-picker-list .label .contextual-display-name {
|
||||||
font-size: 15px;
|
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 {
|
.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 { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||||
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
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 { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||||
import { notebookModeId } from 'sql/common/constants';
|
import { notebookModeId } from 'sql/common/constants';
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ let defaultVal = [
|
|||||||
{
|
{
|
||||||
name: 'Tasks',
|
name: 'Tasks',
|
||||||
widget: {
|
widget: {
|
||||||
'tasks-widget': [{ name: 'restore', when: '!mssql:iscloud' }, 'configureDashboard', 'newQuery']
|
'tasks-widget': [{ name: 'restore', when: '!mssql:iscloud' }, 'configureDashboard', 'newQuery', 'mssqlCluster.task.newNotebook']
|
||||||
},
|
},
|
||||||
gridItemConfig: {
|
gridItemConfig: {
|
||||||
sizex: 1,
|
sizex: 1,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
|
|||||||
let actionContext = {
|
let actionContext = {
|
||||||
ownerUri: ownerUri,
|
ownerUri: ownerUri,
|
||||||
targetObject: targetObject
|
targetObject: targetObject
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let anchor = { x: event.pageX + 1, y: event.pageY };
|
let anchor = { x: event.pageX + 1, y: event.pageY };
|
||||||
@@ -116,7 +117,8 @@ export abstract class JobManagementView extends TabChild implements AfterContent
|
|||||||
{ action: refreshAction },
|
{ action: refreshAction },
|
||||||
{ action: newAction }
|
{ action: newAction }
|
||||||
]);
|
]);
|
||||||
this._actionBar.context = { component: this };
|
let context: IJobActionInfo = { component: this };
|
||||||
|
this._actionBar.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshJobs() {
|
public refreshJobs() {
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-component-node-tile .model-view-tree-node-item-icon{
|
.tree-component-node-tile .model-view-tree-node-item-icon {
|
||||||
width: 15px;
|
width: 17px;
|
||||||
height: 15px;
|
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 icon = this.themeService.getTheme().type === LIGHT ? element.icon : element.iconDark;
|
||||||
const iconUri = icon ? URI.revive(icon) : null;
|
const iconUri = icon ? URI.revive(icon) : null;
|
||||||
templateData.icon.style.backgroundImage = iconUri ? `url('${iconUri.toString(true)}')` : '';
|
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);
|
dom.toggleClass(templateData.icon, 'model-view-tree-node-item-icon', !!icon);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.onCheckedChanged = (checked: boolean) => {
|
element.onCheckedChanged = (checked: boolean) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import 'vs/css!./media/serverTreeActions';
|
import 'vs/css!./media/serverTreeActions';
|
||||||
import * as errors from 'vs/base/common/errors';
|
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 * as builder from 'sql/base/browser/builder';
|
||||||
import Severity from 'vs/base/common/severity';
|
import Severity from 'vs/base/common/severity';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
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 { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||||
import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serverTreeActionProvider';
|
import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serverTreeActionProvider';
|
||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
|
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||||
|
|
||||||
const $ = builder.$;
|
const $ = builder.$;
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ export class ServerTreeView {
|
|||||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.registerCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,6 +98,25 @@ export class ServerTreeView {
|
|||||||
return this._tree;
|
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
|
* 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 { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { Dimension, $, getContentHeight, getContentWidth } from 'vs/base/browser/dom';
|
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 { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||||
import { Builder } from 'sql/base/browser/builder';
|
import { Builder } from 'sql/base/browser/builder';
|
||||||
@@ -211,8 +211,13 @@ export class ChartView extends Disposable implements IPanelView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private buildOptions() {
|
private buildOptions() {
|
||||||
dispose(this.optionDisposables);
|
// The first element in the disposables list is for the chart type: the master dropdown that controls other option controls.
|
||||||
this.optionDisposables = [];
|
// 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 = {
|
this.optionMap = {
|
||||||
'type': this.optionMap['type']
|
'type': this.optionMap['type']
|
||||||
};
|
};
|
||||||
@@ -284,7 +289,7 @@ export class ChartView extends Disposable implements IPanelView {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case ControlType.combo:
|
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.select(option.options.indexOf(value));
|
||||||
dropdown.render(optionContainer);
|
dropdown.render(optionContainer);
|
||||||
dropdown.onDidSelect(e => {
|
dropdown.onDidSelect(e => {
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ export enum JobActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class IJobActionInfo {
|
export class IJobActionInfo {
|
||||||
ownerUri: string;
|
ownerUri?: string;
|
||||||
targetObject: any;
|
targetObject?: any;
|
||||||
component: JobManagementView;
|
component: JobManagementView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +69,11 @@ export class NewJobAction extends Action {
|
|||||||
super(NewJobAction.ID, NewJobAction.LABEL, 'newStepIcon');
|
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) => {
|
return new Promise<boolean>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
await context.openCreateJobDialog();
|
await component.openCreateJobDialog();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
@@ -293,10 +294,11 @@ export class NewAlertAction extends Action {
|
|||||||
super(NewAlertAction.ID, NewAlertAction.LABEL, 'newStepIcon');
|
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) => {
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
context.openCreateAlertDialog();
|
component.openCreateAlertDialog();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
@@ -380,10 +382,11 @@ export class NewOperatorAction extends Action {
|
|||||||
super(NewOperatorAction.ID, NewOperatorAction.LABEL, 'newStepIcon');
|
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) => {
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
context.openCreateOperatorDialog();
|
component.openCreateOperatorDialog();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
@@ -466,10 +469,11 @@ export class NewProxyAction extends Action {
|
|||||||
super(NewProxyAction.ID, NewProxyAction.LABEL, 'newStepIcon');
|
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) => {
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
context.openCreateProxyDialog();
|
component.openCreateProxyDialog();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
|||||||
@@ -13,42 +13,156 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
|||||||
export class SqlTelemetryContribution extends Disposable implements IWorkbenchContribution {
|
export class SqlTelemetryContribution extends Disposable implements IWorkbenchContribution {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ITelemetryService telemetryService: ITelemetryService,
|
@ITelemetryService private telemetryService: ITelemetryService,
|
||||||
@IStorageService storageService: IStorageService
|
@IStorageService storageService: IStorageService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const dailyLastUseDate = Date.parse(storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0'));
|
const dailyLastUseDate: number = Date.parse(storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||||
const weeklyLastUseDate = Date.parse(storageService.get('telemetry.weeklyLastUseDate', StorageScope.GLOBAL, '0'));
|
const weeklyLastUseDate: number = Date.parse(storageService.get('telemetry.weeklyLastUseDate', StorageScope.GLOBAL, '0'));
|
||||||
const monthlyLastUseDate = Date.parse(storageService.get('telemetry.monthlyLastUseDate', 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
|
// daily user event
|
||||||
if (this.diffInDays(Date.parse(today), dailyLastUseDate) >= 1) {
|
if (this.didDayChange(dailyLastUseDate)) {
|
||||||
// daily first use
|
// daily first use
|
||||||
telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true });
|
telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true });
|
||||||
storageService.store('telemetry.dailyLastUseDate', today, StorageScope.GLOBAL);
|
storageService.store('telemetry.dailyLastUseDate', todayString, StorageScope.GLOBAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// weekly user event
|
// weekly user event
|
||||||
if (this.diffInDays(Date.parse(today), weeklyLastUseDate) >= 7) {
|
if (this.didWeekChange(weeklyLastUseDate)) {
|
||||||
// weekly first use
|
// weekly first use
|
||||||
telemetryService.publicLog('telemetry.weeklyFirstUse', { weeklyFirstUse: true });
|
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 });
|
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 {
|
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);
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SqlTelemetryContribution, LifecyclePhase.Starting);
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ export interface INotebookKernelDetails {
|
|||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly supportsIntellisense: boolean;
|
readonly supportsIntellisense: boolean;
|
||||||
|
readonly requiresConnection: boolean;
|
||||||
readonly info?: any;
|
readonly info?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
id: kernel.id,
|
id: kernel.id,
|
||||||
info: kernel.info,
|
info: kernel.info,
|
||||||
name: kernel.name,
|
name: kernel.name,
|
||||||
supportsIntellisense: kernel.supportsIntellisense
|
supportsIntellisense: kernel.supportsIntellisense,
|
||||||
|
requiresConnection: kernel.requiresConnection
|
||||||
};
|
};
|
||||||
return kernelDetails;
|
return kernelDetails;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,13 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
|
|||||||
options.providerId = showOptions.providerId;
|
options.providerId = showOptions.providerId;
|
||||||
options.connectionProfile = showOptions.connectionProfile;
|
options.connectionProfile = showOptions.connectionProfile;
|
||||||
options.defaultKernel = showOptions.defaultKernel;
|
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 id = await this._proxy.$tryShowNotebookDocument(uri, options);
|
||||||
let editor = this.getEditor(id);
|
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 { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, FutureMessageType, INotebookFutureDetails, INotebookFutureDone } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
|
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
|
||||||
import { Deferred } from 'sql/base/common/promise';
|
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)
|
@extHostNamedCustomer(SqlMainContext.MainThreadNotebook)
|
||||||
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
|
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
|
||||||
@@ -352,6 +352,10 @@ class KernelWrapper implements azdata.nb.IKernel {
|
|||||||
return this.kernelDetails.supportsIntellisense;
|
return this.kernelDetails.supportsIntellisense;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get requiresConnection(): boolean {
|
||||||
|
return this.kernelDetails.requiresConnection;
|
||||||
|
}
|
||||||
|
|
||||||
get info(): azdata.nb.IInfoReply {
|
get info(): azdata.nb.IInfoReply {
|
||||||
return this._info;
|
return this._info;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import {
|
|||||||
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
|
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
|
||||||
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData, INotebookModelChangedData
|
||||||
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
} 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 { INotebookService, INotebookEditor, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { disposed } from 'vs/base/common/errors';
|
import { disposed } from 'vs/base/common/errors';
|
||||||
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, NotebookContentChange, INotebookModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { NotebookChangeType, CellTypes } from 'sql/parts/notebook/models/contracts';
|
import { NotebookChangeType, CellTypes } from 'sql/workbench/parts/notebook/models/contracts';
|
||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||||
import { notebookModeId } from 'sql/common/constants';
|
import { notebookModeId } from 'sql/common/constants';
|
||||||
@@ -399,8 +399,8 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
};
|
};
|
||||||
let isUntitled: boolean = uri.scheme === Schemas.untitled;
|
let isUntitled: boolean = uri.scheme === Schemas.untitled;
|
||||||
|
|
||||||
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId) :
|
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId, options.initialContent) :
|
||||||
this._editorService.createInput({ resource: uri, language: notebookModeId });
|
this._editorService.createInput({ resource: uri, language: notebookModeId });
|
||||||
let input = this._instantiationService.createInstance(NotebookInput, path.basename(uri.fsPath), uri, fileInput);
|
let input = this._instantiationService.createInstance(NotebookInput, path.basename(uri.fsPath), uri, fileInput);
|
||||||
input.isTrusted = isUntitled;
|
input.isTrusted = isUntitled;
|
||||||
input.defaultKernel = options.defaultKernel;
|
input.defaultKernel = options.defaultKernel;
|
||||||
|
|||||||
@@ -854,6 +854,7 @@ export interface INotebookShowOptions {
|
|||||||
providerId?: string;
|
providerId?: string;
|
||||||
connectionProfile?: azdata.IConnectionProfile;
|
connectionProfile?: azdata.IConnectionProfile;
|
||||||
defaultKernel?: azdata.nb.IKernelSpec;
|
defaultKernel?: azdata.nb.IKernelSpec;
|
||||||
|
initialContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostNotebookDocumentsAndEditorsShape {
|
export interface ExtHostNotebookDocumentsAndEditorsShape {
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
|||||||
export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
||||||
|
|
||||||
private _root: HTMLElement;
|
private _root: HTMLElement;
|
||||||
private _searchBox: InputBox;
|
|
||||||
private _toDisposeViewlet: IDisposable[] = [];
|
private _toDisposeViewlet: IDisposable[] = [];
|
||||||
private _serverTreeView: ServerTreeView;
|
private _serverTreeView: ServerTreeView;
|
||||||
private _clearSearchAction: ClearSearchAction;
|
|
||||||
private _addServerAction: IAction;
|
private _addServerAction: IAction;
|
||||||
private _addServerGroupAction: IAction;
|
private _addServerGroupAction: IAction;
|
||||||
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
||||||
@@ -53,7 +51,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
|||||||
|
|
||||||
super(VIEWLET_ID, configurationService, layoutService, telemetryService, _themeService, storageService);
|
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,
|
this._addServerAction = this._instantiationService.createInstance(AddServerAction,
|
||||||
AddServerAction.ID,
|
AddServerAction.ID,
|
||||||
AddServerAction.LABEL);
|
AddServerAction.LABEL);
|
||||||
@@ -79,24 +76,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
|||||||
super.create(parent);
|
super.create(parent);
|
||||||
this._root = parent;
|
this._root = parent;
|
||||||
const viewletContainer = DOM.append(parent, DOM.$('div.server-explorer-viewlet'));
|
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'));
|
const viewContainer = DOM.append(viewletContainer, DOM.$('div.object-explorer-view'));
|
||||||
this._serverTreeView.renderBody(viewContainer).then(undefined, error => {
|
this._serverTreeView.renderBody(viewContainer).then(undefined, error => {
|
||||||
warn('render registered servers: ' + error);
|
warn('render registered servers: ' + error);
|
||||||
@@ -105,7 +84,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
|||||||
|
|
||||||
public search(value: string): void {
|
public search(value: string): void {
|
||||||
if (value) {
|
if (value) {
|
||||||
this._clearSearchAction.enabled = true;
|
|
||||||
this._serverTreeView.searchTree(value);
|
this._serverTreeView.searchTree(value);
|
||||||
} else {
|
} else {
|
||||||
this.clearSearch();
|
this.clearSearch();
|
||||||
@@ -129,8 +107,7 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public layout({ height, width }: DOM.Dimension): void {
|
public layout({ height, width }: DOM.Dimension): void {
|
||||||
this._searchBox.layout();
|
this._serverTreeView.layout(height);
|
||||||
this._serverTreeView.layout(height - 36); // account for search box
|
|
||||||
DOM.toggleClass(this._root, 'narrow', width <= 350);
|
DOM.toggleClass(this._root, 'narrow', width <= 350);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,9 +117,6 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet {
|
|||||||
|
|
||||||
public clearSearch() {
|
public clearSearch() {
|
||||||
this._serverTreeView.refreshTree();
|
this._serverTreeView.refreshTree();
|
||||||
this._searchBox.value = '';
|
|
||||||
this._clearSearchAction.enabled = false;
|
|
||||||
this._searchBox.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.server-explorer-viewlet .object-explorer-view {
|
.server-explorer-viewlet .object-explorer-view {
|
||||||
height: calc(100% - 36px);
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-explorer-viewlet .server-group {
|
.server-explorer-viewlet .server-group {
|
||||||
|
|||||||
@@ -31,10 +31,8 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com
|
|||||||
export class ConnectionViewletPanel extends ViewletPanel {
|
export class ConnectionViewletPanel extends ViewletPanel {
|
||||||
|
|
||||||
private _root: HTMLElement;
|
private _root: HTMLElement;
|
||||||
private _searchBox: InputBox;
|
|
||||||
private _toDisposeViewlet: IDisposable[] = [];
|
private _toDisposeViewlet: IDisposable[] = [];
|
||||||
private _serverTreeView: ServerTreeView;
|
private _serverTreeView: ServerTreeView;
|
||||||
private _clearSearchAction: ClearSearchAction;
|
|
||||||
private _addServerAction: IAction;
|
private _addServerAction: IAction;
|
||||||
private _addServerGroupAction: IAction;
|
private _addServerGroupAction: IAction;
|
||||||
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
||||||
@@ -54,7 +52,7 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
|||||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
|
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
|
||||||
) {
|
) {
|
||||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
|
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,
|
this._addServerAction = this.instantiationService.createInstance(AddServerAction,
|
||||||
AddServerAction.ID,
|
AddServerAction.ID,
|
||||||
AddServerAction.LABEL);
|
AddServerAction.LABEL);
|
||||||
@@ -77,24 +75,6 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
|||||||
|
|
||||||
renderBody(container: HTMLElement): void {
|
renderBody(container: HTMLElement): void {
|
||||||
const viewletContainer = DOM.append(container, DOM.$('div.server-explorer-viewlet'));
|
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'));
|
const viewContainer = DOM.append(viewletContainer, DOM.$('div.object-explorer-view'));
|
||||||
this._serverTreeView.renderBody(viewContainer).then(undefined, error => {
|
this._serverTreeView.renderBody(viewContainer).then(undefined, error => {
|
||||||
console.warn('render registered servers: ' + error);
|
console.warn('render registered servers: ' + error);
|
||||||
@@ -103,8 +83,7 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layoutBody(size: number): void {
|
layoutBody(size: number): void {
|
||||||
this._searchBox.layout();
|
this._serverTreeView.layout(size);
|
||||||
this._serverTreeView.layout(size - 46); // account for search box and horizontal scroll bar
|
|
||||||
DOM.toggleClass(this._root, 'narrow', this._root.clientWidth < 300);
|
DOM.toggleClass(this._root, 'narrow', this._root.clientWidth < 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,14 +119,10 @@ export class ConnectionViewletPanel extends ViewletPanel {
|
|||||||
|
|
||||||
public clearSearch() {
|
public clearSearch() {
|
||||||
this._serverTreeView.refreshTree();
|
this._serverTreeView.refreshTree();
|
||||||
this._searchBox.value = '';
|
|
||||||
this._clearSearchAction.enabled = false;
|
|
||||||
this._searchBox.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public search(value: string): void {
|
public search(value: string): void {
|
||||||
if (value) {
|
if (value) {
|
||||||
this._clearSearchAction.enabled = true;
|
|
||||||
this._serverTreeView.searchTree(value);
|
this._serverTreeView.searchTree(value);
|
||||||
} else {
|
} else {
|
||||||
this.clearSearch();
|
this.clearSearch();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
|
|||||||
// Viewlet Action
|
// Viewlet Action
|
||||||
export class OpenDataExplorerViewletAction extends ShowViewletAction {
|
export class OpenDataExplorerViewletAction extends ShowViewletAction {
|
||||||
public static ID = VIEWLET_ID;
|
public static ID = VIEWLET_ID;
|
||||||
public static LABEL = localize('showDataExplorer', "Show Data Explorer");
|
public static LABEL = localize('showDataExplorer', "Show Connections");
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
id: string,
|
id: string,
|
||||||
@@ -39,7 +39,7 @@ export class OpenDataExplorerViewletAction extends ShowViewletAction {
|
|||||||
const viewletDescriptor = new ViewletDescriptor(
|
const viewletDescriptor = new ViewletDescriptor(
|
||||||
DataExplorerViewlet,
|
DataExplorerViewlet,
|
||||||
VIEWLET_ID,
|
VIEWLET_ID,
|
||||||
localize('workbench.dataExplorer', "Data Explorer"),
|
localize('workbench.dataExplorer', "Connections"),
|
||||||
'dataExplorer',
|
'dataExplorer',
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,14 +19,9 @@ import { viewsContainersExtensionPoint } from 'vs/workbench/api/browser/viewsExt
|
|||||||
|
|
||||||
import { CustomTreeView } from 'sql/workbench/browser/parts/views/customView';
|
import { CustomTreeView } from 'sql/workbench/browser/parts/views/customView';
|
||||||
|
|
||||||
export const DataExplorerViewlet = {
|
export const VIEWLET_ID = 'workbench.view.connections';
|
||||||
DataExplorer: 'dataExplorer'
|
|
||||||
};
|
|
||||||
export const VIEWLET_ID = 'workbench.view.dataExplorer';
|
|
||||||
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
|
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface IUserFriendlyViewDescriptor {
|
interface IUserFriendlyViewDescriptor {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -156,7 +151,7 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showCollapsed(container: ViewContainer): boolean {
|
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, {
|
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||||
group: 'connection',
|
group: 'connection',
|
||||||
order: 4,
|
order: 3,
|
||||||
command: {
|
command: {
|
||||||
id: DISCONNECT_COMMAND_ID,
|
id: DISCONNECT_COMMAND_ID,
|
||||||
title: localize('disconnect', 'Disconnect')
|
title: localize('disconnect', 'Disconnect')
|
||||||
@@ -20,6 +20,7 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
|||||||
|
|
||||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||||
group: 'connection',
|
group: 'connection',
|
||||||
|
order: 2,
|
||||||
command: {
|
command: {
|
||||||
id: NEW_QUERY_COMMAND_ID,
|
id: NEW_QUERY_COMMAND_ID,
|
||||||
title: localize('newQuery', 'New Query')
|
title: localize('newQuery', 'New Query')
|
||||||
@@ -29,7 +30,7 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
|||||||
|
|
||||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||||
group: 'connection',
|
group: 'connection',
|
||||||
order: 4,
|
order: 1,
|
||||||
command: {
|
command: {
|
||||||
id: MANAGE_COMMAND_ID,
|
id: MANAGE_COMMAND_ID,
|
||||||
title: localize('manage', 'Manage')
|
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 { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
|
||||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { CellContext, CellActionBase } from 'sql/parts/notebook/cellViews/codeActions';
|
import { CellContext, CellActionBase } from 'sql/workbench/parts/notebook/cellViews/codeActions';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
|
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
|
||||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||||
import { CellModel } from 'sql/parts/notebook/models/cell';
|
import { CellModel } from 'sql/workbench/parts/notebook/models/cell';
|
||||||
|
|
||||||
export const HIDDEN_CLASS ='actionhidden';
|
export const HIDDEN_CLASS ='actionhidden';
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ export class CellToggleMoreActions {
|
|||||||
instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')),
|
instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')),
|
||||||
instantiationService.createInstance(AddCellFromContextAction,'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false),
|
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, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true),
|
||||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false),
|
instantiationService.createInstance(AddCellFromContextAction, 'markdownBefore', localize('markdownBefore', 'Insert Text before'), CellTypes.Markdown, false),
|
||||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true),
|
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', 'Insert Text after'), CellTypes.Markdown, true),
|
||||||
instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', 'Clear output'))
|
instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', 'Clear output'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,15 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import 'vs/css!./code';
|
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 { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||||
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
|
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
|
||||||
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
|
import { CellToggleMoreActions } from 'sql/workbench/parts/notebook/cellToggleMoreActions';
|
||||||
import { ICellModel, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, notebookConstants } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||||
import { RunCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
|
import { RunCellAction, CellContext } from 'sql/workbench/parts/notebook/cellViews/codeActions';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
|
|
||||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
import * as themeColors from 'vs/workbench/common/theme';
|
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 { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
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 { 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 { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
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(IModelService) private _modelService: IModelService,
|
||||||
@Inject(IModeService) private _modeService: IModeService,
|
@Inject(IModeService) private _modeService: IModeService,
|
||||||
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
||||||
@Inject(IConfigurationService) private _configurationService: IConfigurationService
|
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
|
||||||
|
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
|
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
|
||||||
@@ -161,7 +162,14 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
&& this.cellModel.cellUri;
|
&& this.cellModel.cellUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get destroyed(): boolean{
|
||||||
|
return !!(this._changeRef['destroyed']);
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.createEditor();
|
this.createEditor();
|
||||||
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
|
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
|
||||||
this._layoutEmitter.fire();
|
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);
|
let overrideEditorSetting = this._configurationService.getValue<boolean>(OVERRIDE_EDITOR_THEMING_SETTING);
|
||||||
this._editor.hideLineNumbers = (overrideEditorSetting && this.cellModel.cellType === CellTypes.Markdown);
|
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._editor);
|
||||||
this._register(this._editorInput);
|
this._register(this._editorInput);
|
||||||
this._register(this._editorModel.onDidChangeContent(e => {
|
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 * as types from 'vs/base/common/types';
|
||||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
|
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
import { getErrorMessage } from 'sql/workbench/parts/notebook/notebookUtils';
|
||||||
import { ICellModel, CellExecutionState } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, CellExecutionState } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
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");
|
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
|
||||||
const emptyExecutionCountLabel = '[ ]';
|
const emptyExecutionCountLabel = '[ ]';
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
|
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 0 0 auto; width: 100%; height: 100%; display: block">
|
<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>
|
</output-area-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges, AfterViewInit } from '@angular/core';
|
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 { CellView } from 'sql/workbench/parts/notebook/cellViews/interfaces';
|
||||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
|
|
||||||
|
|
||||||
export const CODE_SELECTOR: string = 'code-cell-component';
|
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="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||||
<div style="flex: 0 0 auto; user-select: initial;">
|
<div style="flex: 0 0 auto; user-select: none;">
|
||||||
<div #output ></div>
|
<div #output class="output-userselect" ></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,19 +3,21 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import 'vs/css!./code';
|
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 { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||||
import { nb } from 'azdata';
|
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 { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { MimeModel } from 'sql/parts/notebook/outputs/common/mimemodel';
|
import { MimeModel } from 'sql/workbench/parts/notebook/outputs/common/mimemodel';
|
||||||
import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor';
|
import * as outputProcessor from 'sql/workbench/parts/notebook/outputs/common/outputProcessor';
|
||||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
import { RenderMimeRegistry } from 'sql/workbench/parts/notebook/outputs/registry';
|
||||||
import 'vs/css!sql/parts/notebook/outputs/style/index';
|
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
|
||||||
export const OUTPUT_SELECTOR: string = 'output-component';
|
export const OUTPUT_SELECTOR: string = 'output-component';
|
||||||
|
const USER_SELECT_CLASS ='actionselect';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: OUTPUT_SELECTOR,
|
selector: OUTPUT_SELECTOR,
|
||||||
@@ -28,6 +30,7 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
|||||||
private _trusted: boolean;
|
private _trusted: boolean;
|
||||||
private _initialized: boolean = false;
|
private _initialized: boolean = false;
|
||||||
private readonly _minimumHeight = 30;
|
private readonly _minimumHeight = 30;
|
||||||
|
private _activeCellId: string;
|
||||||
registry: RenderMimeRegistry;
|
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() {
|
private renderOutput() {
|
||||||
let node = this.outputElement.nativeElement;
|
let node = this.outputElement.nativeElement;
|
||||||
let output = this.cellOutput;
|
let output = this.cellOutput;
|
||||||
@@ -63,14 +87,21 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
|||||||
return this._trusted;
|
return this._trusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input() set trustedMode(value: boolean) {
|
||||||
set trustedMode(value: boolean) {
|
|
||||||
this._trusted = value;
|
this._trusted = value;
|
||||||
if (this._initialized) {
|
if (this._initialized) {
|
||||||
this.renderOutput();
|
this.renderOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Input() set activeCellId(value: string) {
|
||||||
|
this._activeCellId = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeCellId(): string {
|
||||||
|
return this._activeCellId;
|
||||||
|
}
|
||||||
|
|
||||||
protected createRenderedMimetype(options: MimeModel.IOptions, node: HTMLElement): void {
|
protected createRenderedMimetype(options: MimeModel.IOptions, node: HTMLElement): void {
|
||||||
let mimeType = this.registry.preferredMimeType(
|
let mimeType = this.registry.preferredMimeType(
|
||||||
options.data,
|
options.data,
|
||||||
@@ -100,4 +131,7 @@ export class OutputComponent extends AngularDisposable implements OnInit {
|
|||||||
//this.setState({ node: node });
|
//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 style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||||
<div #outputarea class="notebook-output" style="flex: 0 0 auto;">
|
<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>
|
</output-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -6,7 +6,7 @@ import 'vs/css!./code';
|
|||||||
import 'vs/css!./outputArea';
|
import 'vs/css!./outputArea';
|
||||||
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef } from '@angular/core';
|
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef } from '@angular/core';
|
||||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
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 * as themeColors from 'vs/workbench/common/theme';
|
||||||
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
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;
|
@ViewChild('outputarea', { read: ElementRef }) private outputArea: ElementRef;
|
||||||
@Input() cellModel: ICellModel;
|
@Input() cellModel: ICellModel;
|
||||||
|
|
||||||
|
private _activeCellId: string;
|
||||||
|
|
||||||
private readonly _minimumHeight = 30;
|
private readonly _minimumHeight = 30;
|
||||||
|
|
||||||
constructor(
|
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 {
|
private updateTheme(theme: IColorTheme): void {
|
||||||
let outputElement = <HTMLElement>this.outputArea.nativeElement;
|
let outputElement = <HTMLElement>this.outputArea.nativeElement;
|
||||||
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||||
@@ -10,4 +10,13 @@ output-area-component .notebook-output {
|
|||||||
border-top-width: 0px;
|
border-top-width: 0px;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
padding: 5px 20px 0px;
|
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 'vs/css!./placeholder';
|
||||||
|
|
||||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core';
|
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core';
|
||||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
import { CellView } from 'sql/workbench/parts/notebook/cellViews/interfaces';
|
||||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { localize } from 'vs/nls';
|
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';
|
export const PLACEHOLDER_SELECTOR: string = 'placeholder-cell-component';
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</code-component>
|
</code-component>
|
||||||
</div>
|
</div>
|
||||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
|
<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>
|
||||||
<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 #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>
|
</div>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
import 'vs/css!./textCell';
|
import 'vs/css!./textCell';
|
||||||
|
|
||||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, OnChanges, SimpleChange } from '@angular/core';
|
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 { localize } from 'vs/nls';
|
||||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
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 { Emitter } from 'vs/base/common/event';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
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 { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
import { CellView } from 'sql/workbench/parts/notebook/cellViews/interfaces';
|
||||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { ISanitizer, defaultSanitizer } from 'sql/parts/notebook/outputs/sanitizer';
|
import { ISanitizer, defaultSanitizer } from 'sql/workbench/parts/notebook/outputs/sanitizer';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { CellToggleMoreActions } from 'sql/parts/notebook/cellToggleMoreActions';
|
import { CellToggleMoreActions } from 'sql/workbench/parts/notebook/cellToggleMoreActions';
|
||||||
|
|
||||||
export const TEXT_SELECTOR: string = 'text-cell-component';
|
export const TEXT_SELECTOR: string = 'text-cell-component';
|
||||||
|
const USER_SELECT_CLASS ='actionselect';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: TEXT_SELECTOR,
|
selector: TEXT_SELECTOR,
|
||||||
@@ -111,6 +114,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
if (propName === 'activeCellId') {
|
if (propName === 'activeCellId') {
|
||||||
let changedProp = changes[propName];
|
let changedProp = changes[propName];
|
||||||
this._activeCellId = changedProp.currentValue;
|
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;
|
// 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()
|
// it will be set to true in a subsequent call to toggleEditMode()
|
||||||
if (changedProp.previousValue !== undefined) {
|
if (changedProp.previousValue !== undefined) {
|
||||||
@@ -133,8 +137,9 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
} else {
|
} else {
|
||||||
this._content = this.sanitizeContent(this.cellModel.source);
|
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;
|
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||||
outputElement.innerHTML = htmlcontent;
|
outputElement.innerHTML = htmlcontent;
|
||||||
});
|
});
|
||||||
@@ -149,6 +154,24 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
return content;
|
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
|
// Todo: implement layout
|
||||||
public 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 {
|
private setFocusAndScroll(): void {
|
||||||
this.toggleEditMode(this.isActive());
|
this.toggleEditMode(this.isActive());
|
||||||
|
|
||||||
@@ -8,7 +8,11 @@ text-cell-component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
text-cell-component .notebook-preview {
|
text-cell-component .notebook-preview {
|
||||||
user-select: initial;
|
user-select: none;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-right: 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 { localize } from 'vs/nls';
|
||||||
|
|
||||||
import * as notebookUtils from '../notebookUtils';
|
import * as notebookUtils from '../notebookUtils';
|
||||||
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
|
import { CellTypes, CellType, NotebookChangeType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { ICellModelOptions, IModelFactory, FutureInternal, CellExecutionState } from './modelInterfaces';
|
import { ICellModelOptions, IModelFactory, FutureInternal, CellExecutionState } from './modelInterfaces';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
@@ -210,6 +210,10 @@ export class CellModel implements ICellModel {
|
|||||||
await kernel.interrupt();
|
await kernel.interrupt();
|
||||||
} else {
|
} else {
|
||||||
// TODO update source based on editor component contents
|
// 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;
|
let content = this.source;
|
||||||
if (content) {
|
if (content) {
|
||||||
let future = await kernel.requestExecute({
|
let future = await kernel.requestExecute({
|
||||||
@@ -386,16 +390,8 @@ export class CellModel implements ICellModel {
|
|||||||
let endpoint = this.getKnoxEndpoint(model.activeConnection);
|
let endpoint = this.getKnoxEndpoint(model.activeConnection);
|
||||||
let host = endpoint && endpoint.ipAddress ? endpoint.ipAddress : model.activeConnection.serverName;
|
let host = endpoint && endpoint.ipAddress ? endpoint.ipAddress : model.activeConnection.serverName;
|
||||||
let html = result.data['text/html'];
|
let html = result.data['text/html'];
|
||||||
html = html.replace(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, function (a, b, c) {
|
html =this.rewriteUrlUsingRegex(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, html, host);
|
||||||
let ret = '';
|
html =this.rewriteUrlUsingRegex(/(https?:\/\/master.*master-svc.*\/proxy)(.*)/g, html, host);
|
||||||
if (b !== '') {
|
|
||||||
ret = 'https://' + host + ':30443/gateway/default/yarn/proxy';
|
|
||||||
}
|
|
||||||
if (c !== '') {
|
|
||||||
ret = ret + c;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
(<nb.IDisplayResult>output).data['text/html'] = html;
|
(<nb.IDisplayResult>output).data['text/html'] = html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,6 +401,19 @@ export class CellModel implements ICellModel {
|
|||||||
return output;
|
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 {
|
private getDisplayId(msg: nb.IIOPubMessage): string | undefined {
|
||||||
let transient = (msg.content.transient || {});
|
let transient = (msg.content.transient || {});
|
||||||
return transient['display_id'] as string;
|
return transient['display_id'] as string;
|
||||||
@@ -420,7 +429,7 @@ export class CellModel implements ICellModel {
|
|||||||
if (this._cellType === CellTypes.Code) {
|
if (this._cellType === CellTypes.Code) {
|
||||||
cellJson.metadata.language = this._language,
|
cellJson.metadata.language = this._language,
|
||||||
cellJson.outputs = this._outputs;
|
cellJson.outputs = this._outputs;
|
||||||
cellJson.execution_count = this.executionCount;
|
cellJson.execution_count = this.executionCount ? this.executionCount : 0;
|
||||||
}
|
}
|
||||||
return cellJson as nb.ICellContents;
|
return cellJson as nb.ICellContents;
|
||||||
}
|
}
|
||||||
@@ -481,7 +490,10 @@ export class CellModel implements ICellModel {
|
|||||||
if (serverInfo && serverInfo.options && serverInfo.options['clusterEndpoints']) {
|
if (serverInfo && serverInfo.options && serverInfo.options['clusterEndpoints']) {
|
||||||
let endpoints: notebookUtils.IEndpoint[] = serverInfo.options['clusterEndpoints'];
|
let endpoints: notebookUtils.IEndpoint[] = serverInfo.options['clusterEndpoints'];
|
||||||
if (endpoints && endpoints.length > 0) {
|
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';
|
'use strict';
|
||||||
|
|
||||||
import { ICellMagicMapper, ILanguageMagic } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellMagicMapper, ILanguageMagic } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
|
|
||||||
const defaultKernel = '*';
|
const defaultKernel = '*';
|
||||||
export class CellMagicMapper implements ICellMagicMapper {
|
export class CellMagicMapper implements ICellMagicMapper {
|
||||||
@@ -14,16 +14,16 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
|||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
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 { INotebookManager } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
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 { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
import { localize } from 'vs/nls';
|
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 {
|
export interface IClientSessionOptions {
|
||||||
notebookUri: URI;
|
notebookUri: URI;
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
import { nb } from 'azdata';
|
import { nb } from 'azdata';
|
||||||
|
|
||||||
import { localize } from 'vs/nls';
|
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 { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
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 { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, NotebookContentChange, notebookConstants } from './modelInterfaces';
|
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 { nbversion } from '../notebookConstants';
|
||||||
import * as notebookUtils from '../notebookUtils';
|
import * as notebookUtils from '../notebookUtils';
|
||||||
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService';
|
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 { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import { URI } from 'vs/base/common/uri';
|
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 { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
import { CellTypes, CellType } from 'sql/workbench/parts/notebook/models/contracts';
|
||||||
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/workbench/parts/notebook/models/modelInterfaces';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
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 { 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 { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel';
|
||||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
import { ModelFactory } from 'sql/workbench/parts/notebook/models/modelFactory';
|
||||||
import * as notebookUtils from 'sql/parts/notebook/notebookUtils';
|
import * as notebookUtils from 'sql/workbench/parts/notebook/notebookUtils';
|
||||||
import { Deferred } from 'sql/base/common/promise';
|
import { Deferred } from 'sql/base/common/promise';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
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 { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
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 { 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';
|
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 { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
|
|
||||||
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInput } from 'sql/workbench/parts/notebook/notebookInput';
|
||||||
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
|
import { NotebookEditor } from 'sql/workbench/parts/notebook/notebookEditor';
|
||||||
|
|
||||||
// Model View editor registration
|
// Model View editor registration
|
||||||
const viewModelEditorDescriptor = new EditorDescriptor(
|
const viewModelEditorDescriptor = new EditorDescriptor(
|
||||||