Merge branch 'master' into release/1.5

This commit is contained in:
Karl Burtram
2019-03-14 10:04:56 -07:00
27 changed files with 274 additions and 222 deletions

View File

@@ -1,49 +1,49 @@
{ {
perform: true, perform: true,
alwaysRequireAssignee: false, alwaysRequireAssignee: false,
labelsRequiringAssignee: [], labelsRequiringAssignee: [],
autoAssignees: { autoAssignees: {
accessibility: [], accessibility: [],
acquisition: [], acquisition: [],
agent: [], agent: [],
azure: [], azure: [],
backup: [], backup: [],
bcdr: [], bcdr: [],
'chart viewer': [], 'chart viewer': [],
connection: [], connection: [],
dacfx: [], dacfx: [],
dashboard: [], dashboard: [],
'data explorer': [], 'data explorer': [],
documentation: [], documentation: [],
'edit data': [], 'edit data': [],
export: [], export: [],
extensibility: [], extensibility: [],
extensionManager: [], extensionManager: [],
globalization: [], globalization: [],
grid: [], grid: [],
import: [], import: [],
insights: [], insights: [],
intellisense: [], intellisense: [],
localization: [], localization: [],
'managed instance': [], 'managed instance': [],
notebooks: [], notebooks: [],
'object explorer': [], 'object explorer': [],
performance: [], performance: [],
profiler: [], profiler: [],
'query editor': [], 'query editor': [],
'query execution': [], 'query execution': [],
reliability: [], reliability: [],
restore: [], restore: [],
scripting: [], scripting: [],
'server group': [], 'server group': [],
settings: [], settings: [],
setup: [], setup: [],
shell: [], shell: [],
showplan: [], showplan: [],
snippet: [], snippet: [],
sql2019Preview: [], sql2019Preview: [],
sqldw: [], sqldw: [],
supportability: [], supportability: [],
ux: [] ux: []
} }
} }

20
.github/commands.yml vendored
View File

@@ -1,12 +1,12 @@
{ {
perform: false, perform: false,
commands: [ commands: [
{ {
type: 'label', type: 'label',
name: 'duplicate', name: 'duplicate',
allowTriggerByBot: true, allowTriggerByBot: true,
action: 'close', action: 'close',
comment: "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" comment: "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
} }
] ]
} }

8
.github/locker.yml vendored
View File

@@ -1,6 +1,6 @@
{ {
daysAfterClose: 45, daysAfterClose: 45,
daysSinceLastUpdate: 3, daysSinceLastUpdate: 3,
ignoredLabels: [], ignoredLabels: [],
perform: false perform: true
} }

View File

@@ -1,6 +1,6 @@
{ {
daysUntilClose: 7, daysUntilClose: 7,
needsMoreInfoLabel: 'more info', needsMoreInfoLabel: 'more info',
perform: false, perform: false,
closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
} }

View File

@@ -1,6 +1,6 @@
{ {
newReleaseLabel: 'new-release', newReleaseLabel: 'new-release',
newReleaseColor: '006b75', newReleaseColor: '006b75',
daysAfterRelease: 5, daysAfterRelease: 5,
perform: true perform: true
} }

View File

@@ -1,5 +1,5 @@
{ {
perform: false, perform: true,
whenCreatedByTeam: true, whenCreatedByTeam: true,
comment: "Thanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}" comment: "Thanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}"
} }

View File

@@ -11,8 +11,9 @@ import * as azdata from 'azdata';
import * as fs from 'fs'; import * as fs from 'fs';
import * as utils from '../common/utils'; import * as utils from '../common/utils';
import { AppContext } from '../common/appContext';
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation'; import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
import { ApiWrapper } from '../common/apiWrapper';
import { Deferred } from '../common/promise';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -31,27 +32,44 @@ export class ConfigurePythonDialog {
private pythonLocationTextBox: azdata.InputBoxComponent; private pythonLocationTextBox: azdata.InputBoxComponent;
private browseButton: azdata.ButtonComponent; private browseButton: azdata.ButtonComponent;
constructor(private appContext: AppContext, private outputChannel: vscode.OutputChannel, private jupyterInstallation: JupyterServerInstallation) { private _setupComplete: Deferred<void>;
constructor(private apiWrapper: ApiWrapper, private outputChannel: vscode.OutputChannel, private jupyterInstallation: JupyterServerInstallation) {
this._setupComplete = new Deferred<void>();
} }
public async showDialog() { /**
* Opens a dialog to configure python installation for notebooks.
* @param rejectOnCancel Specifies whether an error should be thrown after clicking Cancel.
* @returns A promise that is resolved when the python installation completes.
*/
public showDialog(rejectOnCancel: boolean = false): Promise<void> {
this.dialog = azdata.window.createModelViewDialog(this.DialogTitle); this.dialog = azdata.window.createModelViewDialog(this.DialogTitle);
this.initializeContent(); this.initializeContent();
this.dialog.okButton.label = this.OkButtonText; this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = this.CancelButtonText; this.dialog.cancelButton.label = this.CancelButtonText;
this.dialog.cancelButton.onClick(() => {
if (rejectOnCancel) {
this._setupComplete.reject(localize('pythonInstallDeclined', 'Python installation was declined.'));
} else {
this._setupComplete.resolve();
}
});
this.dialog.registerCloseValidator(() => this.handleInstall()); this.dialog.registerCloseValidator(() => this.handleInstall());
azdata.window.openDialog(this.dialog); azdata.window.openDialog(this.dialog);
return this._setupComplete.promise;
} }
private initializeContent() { private initializeContent(): void {
this.dialog.registerContent(async view => { this.dialog.registerContent(async view => {
this.pythonLocationTextBox = view.modelBuilder.inputBox() this.pythonLocationTextBox = view.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({ .withProperties<azdata.InputBoxProperties>({
value: JupyterServerInstallation.getPythonInstallPath(this.appContext.apiWrapper), value: JupyterServerInstallation.getPythonInstallPath(this.apiWrapper),
width: '100%' width: '100%'
}).component(); }).component();
@@ -106,14 +124,18 @@ export class ConfigurePythonDialog {
return false; return false;
} }
} catch (err) { } catch (err) {
this.appContext.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
return false; return false;
} }
// 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).catch(err => { this.jupyterInstallation.startInstallProcess(pythonLocation)
this.appContext.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); .then(() => {
}); this._setupComplete.resolve();
})
.catch(err => {
this._setupComplete.reject(utils.getErrorMessage(err));
});
return true; return true;
} }
@@ -149,20 +171,13 @@ export class ConfigurePythonDialog {
openLabel: this.SelectFileLabel openLabel: this.SelectFileLabel
}; };
let fileUris: vscode.Uri[] = await this.appContext.apiWrapper.showOpenDialog(options); let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
if (fileUris && fileUris[0]) { if (fileUris && fileUris[0]) {
this.pythonLocationTextBox.value = fileUris[0].fsPath; this.pythonLocationTextBox.value = fileUris[0].fsPath;
} }
} }
private showInfoMessage(message: string) { private showErrorMessage(message: string): void {
this.dialog.message = {
text: message,
level: azdata.window.MessageLevel.Information
};
}
private showErrorMessage(message: string) {
this.dialog.message = { this.dialog.message = {
text: message, text: message,
level: azdata.window.MessageLevel.Error level: azdata.window.MessageLevel.Error

View File

@@ -127,6 +127,6 @@ function writeNotebookToFile(pythonNotebook: INotebook): vscode.Uri {
async function ensureJupyterInstalled(): Promise<void> { async function ensureJupyterInstalled(): Promise<void> {
let jupterControllerExports = vscode.extensions.getExtension('Microsoft.sql-vnext').exports; let jupterControllerExports = vscode.extensions.getExtension('Microsoft.sql-vnext').exports;
let jupyterController = jupterControllerExports.getJupterController() as JupyterController; let jupyterController = jupterControllerExports.getJupterController() as JupyterController;
await jupyterController.jupyterInstallation; await jupyterController.jupyterInstallation.installReady;
} }

View File

@@ -30,7 +30,7 @@ import CodeAdapter from '../prompts/adapter';
let untitledCounter = 0; let untitledCounter = 0;
export class JupyterController implements vscode.Disposable { export class JupyterController implements vscode.Disposable {
private _jupyterInstallation: Promise<JupyterServerInstallation>; private _jupyterInstallation: JupyterServerInstallation;
private _notebookInstances: IServerInstance[] = []; private _notebookInstances: IServerInstance[] = [];
private outputChannel: vscode.OutputChannel; private outputChannel: vscode.OutputChannel;
@@ -55,31 +55,11 @@ export class JupyterController implements vscode.Disposable {
// PUBLIC METHODS ////////////////////////////////////////////////////// // PUBLIC METHODS //////////////////////////////////////////////////////
public async activate(): Promise<boolean> { public async activate(): Promise<boolean> {
// Prompt for install if the python installation path is not defined this._jupyterInstallation = new JupyterServerInstallation(
let jupyterInstaller = new JupyterServerInstallation(
this.extensionContext.extensionPath, this.extensionContext.extensionPath,
this.outputChannel, this.outputChannel,
this.apiWrapper); this.apiWrapper);
if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
this._jupyterInstallation = Promise.resolve(jupyterInstaller);
} else {
this._jupyterInstallation = new Promise(resolve => {
jupyterInstaller.onInstallComplete(err => {
if (!err) {
resolve(jupyterInstaller);
}
});
});
}
let notebookProvider = undefined;
notebookProvider = this.registerNotebookProvider();
azdata.nb.onDidOpenNotebookDocument(notebook => {
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
this.doConfigurePython(jupyterInstaller);
}
});
// Add command/task handlers // Add command/task handlers
this.apiWrapper.registerTaskHandler(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => { this.apiWrapper.registerTaskHandler(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
return this.handleOpenNotebookTask(profile); return this.handleOpenNotebookTask(profile);
@@ -96,11 +76,12 @@ export class JupyterController implements vscode.Disposable {
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); }); this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
this.apiWrapper.registerCommand(constants.jupyterInstallPackages, () => { return this.doManagePackages(); }); this.apiWrapper.registerCommand(constants.jupyterInstallPackages, () => { return this.doManagePackages(); });
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(jupyterInstaller); }); this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
let supportedFileFilter: vscode.DocumentFilter[] = [ let supportedFileFilter: vscode.DocumentFilter[] = [
{ scheme: 'untitled', language: '*' } { scheme: 'untitled', language: '*' }
]; ];
let notebookProvider = this.registerNotebookProvider();
this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider))); this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider)));
return true; return true;
@@ -195,7 +176,7 @@ export class JupyterController implements vscode.Disposable {
private async handleDependenciesReinstallation(): Promise<void> { private async handleDependenciesReinstallation(): Promise<void> {
if (await this.confirmReinstall()) { if (await this.confirmReinstall()) {
this._jupyterInstallation = JupyterServerInstallation.getInstallation( this._jupyterInstallation = await JupyterServerInstallation.getInstallation(
this.extensionContext.extensionPath, this.extensionContext.extensionPath,
this.outputChannel, this.outputChannel,
this.apiWrapper, this.apiWrapper,
@@ -225,14 +206,11 @@ export class JupyterController implements vscode.Disposable {
} }
} }
public async doConfigurePython(jupyterInstaller: JupyterServerInstallation): Promise<void> { public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
try { let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, jupyterInstaller);
let pythonDialog = new ConfigurePythonDialog(this.appContext, this.outputChannel, jupyterInstaller); pythonDialog.showDialog().catch(err => {
await pythonDialog.showDialog(); this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
} catch (error) { });
let message = utils.getErrorMessage(error);
this.apiWrapper.showErrorMessage(message);
}
} }
public getTextToSendToTerminal(shellType: any): string { public getTextToSendToTerminal(shellType: any): string {

View File

@@ -16,7 +16,9 @@ import * as request from 'request';
import { ApiWrapper } from '../common/apiWrapper'; import { ApiWrapper } from '../common/apiWrapper';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import * as utils from '../common/utils'; import * as utils from '../common/utils';
import { OutputChannel, ConfigurationTarget, Event, EventEmitter, window } from 'vscode'; import { OutputChannel, ConfigurationTarget, window } from 'vscode';
import { Deferred } from '../common/promise';
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const msgPythonInstallationProgress = localize('msgPythonInstallationProgress', 'Python installation is in progress'); const msgPythonInstallationProgress = localize('msgPythonInstallationProgress', 'Python installation is in progress');
@@ -53,7 +55,7 @@ export default class JupyterServerInstallation {
private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python'); private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
private _installCompleteEmitter = new EventEmitter<string>(); private _installReady: Deferred<void>;
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string, forceInstall?: boolean) { constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string, forceInstall?: boolean) {
this.extensionPath = extensionPath; this.extensionPath = extensionPath;
@@ -63,10 +65,15 @@ export default class JupyterServerInstallation {
this._forceInstall = !!forceInstall; this._forceInstall = !!forceInstall;
this.configurePackagePaths(); this.configurePackagePaths();
this._installReady = new Deferred<void>();
if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
this._installReady.resolve();
}
} }
public get onInstallComplete(): Event<string> { public get installReady(): Promise<void> {
return this._installCompleteEmitter.event; return this._installReady.promise;
} }
public static async getInstallation( public static async getInstallation(
@@ -232,7 +239,7 @@ export default class JupyterServerInstallation {
}; };
} }
public async startInstallProcess(pythonInstallationPath?: string): Promise<void> { public startInstallProcess(pythonInstallationPath?: string): Promise<void> {
if (pythonInstallationPath) { if (pythonInstallationPath) {
this._pythonInstallationPath = pythonInstallationPath; this._pythonInstallationPath = pythonInstallationPath;
this.configurePackagePaths(); this.configurePackagePaths();
@@ -249,23 +256,31 @@ export default class JupyterServerInstallation {
operation: op => { operation: op => {
this.installDependencies(op) this.installDependencies(op)
.then(() => { .then(() => {
this._installCompleteEmitter.fire(); this._installReady.resolve();
updateConfig(); updateConfig();
}) })
.catch(err => { .catch(err => {
let errorMsg = msgDependenciesInstallationFailed(err); let errorMsg = msgDependenciesInstallationFailed(err);
op.updateStatus(azdata.TaskStatus.Failed, errorMsg); op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
this.apiWrapper.showErrorMessage(errorMsg); this.apiWrapper.showErrorMessage(errorMsg);
this._installCompleteEmitter.fire(errorMsg); this._installReady.reject(errorMsg);
}); });
} }
}); });
} 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._installCompleteEmitter.fire(); this._installReady.resolve();
updateConfig(); updateConfig();
} }
return this._installReady.promise;
}
public async promptForPythonInstall(): Promise<void> {
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, this);
return pythonDialog.showDialog(true);
}
} }
private async installJupyterProsePackage(): Promise<void> { private async installJupyterProsePackage(): Promise<void> {

View File

@@ -20,7 +20,7 @@ import { PerNotebookServerInstance, IInstanceOptions } from './serverInstance';
export interface IServerManagerOptions { export interface IServerManagerOptions {
documentPath: string; documentPath: string;
jupyterInstallation: Promise<JupyterServerInstallation>; jupyterInstallation: JupyterServerInstallation;
extensionContext: vscode.ExtensionContext; extensionContext: vscode.ExtensionContext;
apiWrapper?: ApiWrapper; apiWrapper?: ApiWrapper;
factory?: ServerInstanceFactory; factory?: ServerInstanceFactory;
@@ -62,7 +62,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
this._onServerStarted.fire(); this._onServerStarted.fire();
} catch (error) { } catch (error) {
this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error {0}', utils.getErrorMessage(error))); this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error: {0}', utils.getErrorMessage(error)));
throw error; throw error;
} }
} }
@@ -102,7 +102,8 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
} }
private async doStartServer(): Promise<IServerInstance> { // We can't find or create servers until the installation is complete private async doStartServer(): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
let installation = await this.options.jupyterInstallation; let installation = this.options.jupyterInstallation;
await installation.promptForPythonInstall();
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the // Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
// notebook to open. This will be the workspace folder if the notebook uri is inside a workspace // notebook to open. This will be the workspace folder if the notebook uri is inside a workspace

View File

@@ -23,7 +23,7 @@ import { MockExtensionContext } from '../common/stubs';
describe('Local Jupyter Server Manager', function (): void { describe('Local Jupyter Server Manager', function (): void {
let expectedPath = 'my/notebook.ipynb'; let expectedPath = 'my/notebook.ipynb';
let serverManager: LocalJupyterServerManager; let serverManager: LocalJupyterServerManager;
let deferredInstall: Deferred<JupyterServerInstallation>; let deferredInstall: Deferred<void>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>; let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockExtensionContext: MockExtensionContext; let mockExtensionContext: MockExtensionContext;
let mockFactory: TypeMoq.IMock<ServerInstanceFactory>; let mockFactory: TypeMoq.IMock<ServerInstanceFactory>;
@@ -33,10 +33,14 @@ describe('Local Jupyter Server Manager', function (): void {
mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny())); mockApiWrapper.setup(a => a.showErrorMessage(TypeMoq.It.isAny()));
mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined); mockApiWrapper.setup(a => a.getWorkspacePathFromUri(TypeMoq.It.isAny())).returns(() => undefined);
mockFactory = TypeMoq.Mock.ofType(ServerInstanceFactory); mockFactory = TypeMoq.Mock.ofType(ServerInstanceFactory);
deferredInstall = new Deferred<JupyterServerInstallation>();
deferredInstall = new Deferred<void>();
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
mockInstall.setup(j => j.promptForPythonInstall()).returns(() => deferredInstall.promise);
serverManager = new LocalJupyterServerManager({ serverManager = new LocalJupyterServerManager({
documentPath: expectedPath, documentPath: expectedPath,
jupyterInstallation: deferredInstall.promise, jupyterInstallation: mockInstall.object,
extensionContext: mockExtensionContext, extensionContext: mockExtensionContext,
apiWrapper: mockApiWrapper.object, apiWrapper: mockApiWrapper.object,
factory: mockFactory.object factory: mockFactory.object
@@ -58,8 +62,8 @@ describe('Local Jupyter Server Manager', function (): void {
it('Should configure and start install', async function (): Promise<void> { it('Should configure and start install', async function (): Promise<void> {
// Given an install and instance that start with no issues // Given an install and instance that start with no issues
let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk'); let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk');
let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri); let mockServerInstance = initInstallAndInstance(expectedUri);
deferredInstall.resolve(mockInstall.object); deferredInstall.resolve();
// When I start the server // When I start the server
let notified = false; let notified = false;
@@ -83,9 +87,9 @@ describe('Local Jupyter Server Manager', function (): void {
it('Should call stop on server instance', async function (): Promise<void> { it('Should call stop on server instance', async function (): Promise<void> {
// Given an install and instance that start with no issues // Given an install and instance that start with no issues
let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk'); let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk');
let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri); let mockServerInstance = initInstallAndInstance(expectedUri);
mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve()); mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve());
deferredInstall.resolve(mockInstall.object); deferredInstall.resolve();
// When I start and then the server // When I start and then the server
await serverManager.startServer(); await serverManager.startServer();
@@ -98,9 +102,9 @@ describe('Local Jupyter Server Manager', function (): void {
it('Should call stop when extension is disposed', async function (): Promise<void> { it('Should call stop when extension is disposed', async function (): Promise<void> {
// Given an install and instance that start with no issues // Given an install and instance that start with no issues
let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk'); let expectedUri = vscode.Uri.parse('http://localhost:1234?token=abcdefghijk');
let [mockInstall, mockServerInstance] = initInstallAndInstance(expectedUri); let mockServerInstance = initInstallAndInstance(expectedUri);
mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve()); mockServerInstance.setup(s => s.stop()).returns(() => Promise.resolve());
deferredInstall.resolve(mockInstall.object); deferredInstall.resolve();
// When I start and then dispose the extension // When I start and then dispose the extension
await serverManager.startServer(); await serverManager.startServer();
@@ -111,13 +115,12 @@ describe('Local Jupyter Server Manager', function (): void {
mockServerInstance.verify(s => s.stop(), TypeMoq.Times.once()); mockServerInstance.verify(s => s.stop(), TypeMoq.Times.once());
}); });
function initInstallAndInstance(uri: vscode.Uri): [TypeMoq.IMock<JupyterServerInstallation>, TypeMoq.IMock<IServerInstance>] { function initInstallAndInstance(uri: vscode.Uri): TypeMoq.IMock<IServerInstance> {
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
let mockServerInstance = TypeMoq.Mock.ofType(JupyterServerInstanceStub); let mockServerInstance = TypeMoq.Mock.ofType(JupyterServerInstanceStub);
mockFactory.setup(f => f.createInstance(TypeMoq.It.isAny())).returns(() => mockServerInstance.object); mockFactory.setup(f => f.createInstance(TypeMoq.It.isAny())).returns(() => mockServerInstance.object);
mockServerInstance.setup(s => s.configure()).returns(() => Promise.resolve()); mockServerInstance.setup(s => s.configure()).returns(() => Promise.resolve());
mockServerInstance.setup(s => s.start()).returns(() => Promise.resolve()); mockServerInstance.setup(s => s.start()).returns(() => Promise.resolve());
mockServerInstance.setup(s => s.uri).returns(() => uri); mockServerInstance.setup(s => s.uri).returns(() => uri);
return [mockInstall, mockServerInstance]; return mockServerInstance;
} }
}); });

View File

@@ -7,6 +7,7 @@ import 'vs/css!./code';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild } from '@angular/core'; import { OnInit, Component, Input, Inject, ElementRef, ViewChild } 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 { 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/parts/notebook/outputs/common/mimemodel';
import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor'; import * as outputProcessor from 'sql/parts/notebook/outputs/common/outputProcessor';
@@ -22,6 +23,7 @@ export const OUTPUT_SELECTOR: string = 'output-component';
export class OutputComponent extends AngularDisposable implements OnInit { export class OutputComponent extends AngularDisposable implements OnInit {
@ViewChild('output', { read: ElementRef }) private outputElement: ElementRef; @ViewChild('output', { read: ElementRef }) private outputElement: ElementRef;
@Input() cellOutput: nb.ICellOutput; @Input() cellOutput: nb.ICellOutput;
@Input() cellModel: ICellModel;
private _trusted: boolean; private _trusted: boolean;
private _initialized: boolean = false; private _initialized: boolean = false;
private readonly _minimumHeight = 30; private readonly _minimumHeight = 30;
@@ -38,6 +40,9 @@ export class OutputComponent extends AngularDisposable implements OnInit {
ngOnInit() { ngOnInit() {
this.renderOutput(); this.renderOutput();
this._initialized = true; this._initialized = true;
this.cellModel.notebookModel.layoutChanged(() => {
this.renderOutput();
});
} }
private renderOutput() { private renderOutput() {

View File

@@ -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" > <output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" [cellModel]="cellModel">
</output-component> </output-component>
</div> </div>
</div> </div>

View File

@@ -129,6 +129,10 @@ export class CellModel implements ICellModel {
return this._cellUri; return this._cellUri;
} }
public get notebookModel(): NotebookModel {
return <NotebookModel>this.options.notebook;
}
public set cellUri(value: URI) { public set cellUri(value: URI) {
this._cellUri = value; this._cellUri = value;
} }
@@ -208,12 +212,12 @@ export class CellModel implements ICellModel {
// TODO update source based on editor component contents // TODO update source based on editor component contents
let content = this.source; let content = this.source;
if (content) { if (content) {
this.fireExecutionStateChanged();
let future = await kernel.requestExecute({ let future = await kernel.requestExecute({
code: content, code: content,
stop_on_error: true stop_on_error: true
}, false); }, false);
this.setFuture(future as FutureInternal); this.setFuture(future as FutureInternal);
this.fireExecutionStateChanged();
// For now, await future completion. Later we should just track and handle cancellation based on model notifications // For now, await future completion. Later we should just track and handle cancellation based on model notifications
let result: nb.IExecuteReplyMsg = <nb.IExecuteReplyMsg><any>await future.done; let result: nb.IExecuteReplyMsg = <nb.IExecuteReplyMsg><any>await future.done;
if (result && result.content) { if (result && result.content) {

View File

@@ -23,6 +23,7 @@ import { IStandardKernelWithProvider } from 'sql/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';
export interface IClientSessionOptions { export interface IClientSessionOptions {
notebookUri: URI; notebookUri: URI;
@@ -449,8 +450,9 @@ export interface ICellModel {
readonly outputs: ReadonlyArray<nb.ICellOutput>; readonly outputs: ReadonlyArray<nb.ICellOutput>;
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>; readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
readonly onExecutionStateChange: Event<CellExecutionState>; readonly onExecutionStateChange: Event<CellExecutionState>;
setFuture(future: FutureInternal): void;
readonly executionState: CellExecutionState; readonly executionState: CellExecutionState;
readonly notebookModel: NotebookModel;
setFuture(future: FutureInternal): void;
runCell(notificationService?: INotificationService, connectionManagementService?: IConnectionManagementService): Promise<boolean>; runCell(notificationService?: INotificationService, connectionManagementService?: IConnectionManagementService): Promise<boolean>;
setOverrideLanguage(language: string); setOverrideLanguage(language: string);
equals(cellModel: ICellModel): boolean; equals(cellModel: ICellModel): boolean;

View File

@@ -24,7 +24,7 @@ import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/co
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/parts/notebook/models/contracts';
import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces'; import { ICellModel, IModelFactory, INotebookModel, NotebookContentChange } from 'sql/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';
@@ -38,8 +38,6 @@ import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction } from
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 { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
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/parts/notebook/models/cellMagicMapper';
@@ -254,8 +252,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
this.detectChanges(); this.detectChanges();
} }
private async setNotebookManager() { private async setNotebookManager(): Promise<void> {
for (let providerId of this._notebookParams.providers) { let providerInfo = await this._notebookParams.providerInfo;
for (let providerId of providerInfo.providers) {
let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri); let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri);
this.notebookManagers.push(notebookManager); this.notebookManagers.push(notebookManager);
} }
@@ -265,12 +264,14 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
// Wait on registration for now. Long-term would be good to cache and refresh // Wait on registration for now. Long-term would be good to cache and refresh
await this.notebookService.registrationComplete; await this.notebookService.registrationComplete;
// Refresh the provider if we had been using default // Refresh the provider if we had been using default
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) { let providerInfo = await this._notebookParams.providerInfo;
if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) {
let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService); let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService);
let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER); let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER);
this._notebookParams.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0]; providerInfo.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0];
} }
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) { if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) {
// If it's still the default, warn them they should install an extension // If it's still the default, warn them they should install an extension
this.notificationService.prompt(Severity.Warning, this.notificationService.prompt(Severity.Warning,
localize('noKernelInstalled', 'Please install the SQL Server 2019 extension to run cells'), localize('noKernelInstalled', 'Please install the SQL Server 2019 extension to run cells'),

View File

@@ -15,7 +15,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { NotebookInput } from 'sql/parts/notebook/notebookInput'; import { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { NotebookModule } from 'sql/parts/notebook/notebook.module'; import { NotebookModule } from 'sql/parts/notebook/notebook.module';
import { NOTEBOOK_SELECTOR } from 'sql/parts/notebook/notebook.component'; import { NOTEBOOK_SELECTOR } from 'sql/parts/notebook/notebook.component';
import { INotebookParams, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookParams } from 'sql/workbench/services/notebook/common/notebookService';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { $ } from 'sql/base/browser/builder'; import { $ } from 'sql/base/browser/builder';
@@ -92,8 +92,7 @@ export class NotebookEditor extends BaseEditor {
let params: INotebookParams = { let params: INotebookParams = {
notebookUri: input.notebookUri, notebookUri: input.notebookUri,
input: input, input: input,
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER, providerInfo: input.getProviderInfo(),
providers: input.providers ? input.providers : [DEFAULT_NOTEBOOK_PROVIDER],
isTrusted: input.isTrusted, isTrusted: input.isTrusted,
profile: input.connectionProfile profile: input.connectionProfile
}; };

View File

@@ -15,7 +15,7 @@ import * as resources from 'vs/base/common/resources';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { IStandardKernelWithProvider, getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils'; import { IStandardKernelWithProvider, getProvidersForFileName, getStandardKernelsForProvider } from 'sql/parts/notebook/notebookUtils';
import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { INotebookModel, IContentManager } from 'sql/parts/notebook/models/modelInterfaces'; import { INotebookModel, IContentManager } from 'sql/parts/notebook/models/modelInterfaces';
@@ -29,6 +29,7 @@ import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/c
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager'; import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>; export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
@@ -140,6 +141,7 @@ export class NotebookInput extends EditorInput {
private _model: NotebookEditorModel; private _model: NotebookEditorModel;
private _untitledEditorService: IUntitledEditorService; private _untitledEditorService: IUntitledEditorService;
private _contentManager: IContentManager; private _contentManager: IContentManager;
private _providersLoaded: Promise<void>;
constructor(private _title: string, constructor(private _title: string,
private resource: URI, private resource: URI,
@@ -147,13 +149,14 @@ export class NotebookInput extends EditorInput {
@ITextModelService private textModelService: ITextModelService, @ITextModelService private textModelService: ITextModelService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService, @IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IInstantiationService private instantiationService: IInstantiationService, @IInstantiationService private instantiationService: IInstantiationService,
@INotebookService private notebookService: INotebookService @INotebookService private notebookService: INotebookService,
@IExtensionService private extensionService: IExtensionService
) { ) {
super(); super();
this._untitledEditorService = untitledEditorService; this._untitledEditorService = untitledEditorService;
this.resource = resource; this.resource = resource;
this._standardKernels = []; this._standardKernels = [];
this.assignProviders(); this._providersLoaded = this.assignProviders();
} }
public get textInput(): UntitledEditorInput { public get textInput(): UntitledEditorInput {
@@ -186,14 +189,13 @@ export class NotebookInput extends EditorInput {
return this._title; return this._title;
} }
public get providerId(): string { public async getProviderInfo(): Promise<IProviderInfo> {
return this._providerId; await this._providersLoaded;
return {
providerId: this._providerId ? this._providerId : DEFAULT_NOTEBOOK_PROVIDER,
providers: this._providers ? this._providers : [DEFAULT_NOTEBOOK_PROVIDER]
};
} }
public set providerId(value: string) {
this._providerId = value;
}
public get isTrusted(): boolean { public get isTrusted(): boolean {
return this._isTrusted; return this._isTrusted;
} }
@@ -214,14 +216,6 @@ export class NotebookInput extends EditorInput {
return this._standardKernels; return this._standardKernels;
} }
public get providers(): string[] {
return this._providers;
}
public set providers(value: string[]) {
this._providers = value;
}
public save(): TPromise<boolean> { public save(): TPromise<boolean> {
let options: ISaveOptions = { force: false }; let options: ISaveOptions = { force: false };
return this._model.save(options); return this._model.save(options);
@@ -280,7 +274,8 @@ export class NotebookInput extends EditorInput {
} }
} }
private assignProviders(): void { private async assignProviders(): Promise<void> {
await this.extensionService.whenInstalledExtensionsRegistered();
let providerIds: string[] = getProvidersForFileName(this._title, this.notebookService); let providerIds: string[] = getProvidersForFileName(this._title, this.notebookService);
if (providerIds && providerIds.length > 0) { if (providerIds && providerIds.length > 0) {
this._providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0]; this._providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0];

View File

@@ -42,6 +42,10 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi
box-shadow: 0; box-shadow: 0;
} }
.notebookEditor .notebook-cell.active:hover {
border-color: ${activeBorder};
}
.notebookEditor .notebook-cell:hover:not(.active) { .notebookEditor .notebook-cell:hover:not(.active) {
box-shadow: ${lightBoxShadow}; box-shadow: ${lightBoxShadow};
} }
@@ -98,13 +102,14 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi
// Standard notebook cell behavior // Standard notebook cell behavior
collector.addRule(` collector.addRule(`
.notebookEditor .notebook-cell { .notebookEditor .notebook-cell {
border-color: ${inactiveBorder}; border-color: transparent;
border-width: 1px; border-width: 1px;
} }
.notebookEditor .notebook-cell.active { .notebookEditor .notebook-cell.active {
border-width: 1px; border-width: 1px;
} }
.notebookEditor .notebook-cell:hover { .notebookEditor .notebook-cell:hover {
border-color: ${inactiveBorder};
border-width: 1px; border-width: 1px;
} }
`); `);

View File

@@ -32,6 +32,7 @@ import { TreeNode, TreeItemCollapsibleState } from 'sql/parts/objectExplorer/com
import { SERVER_GROUP_CONFIG, SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroup.contribution'; import { SERVER_GROUP_CONFIG, SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroup.contribution';
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';
const $ = builder.$; const $ = builder.$;
@@ -54,7 +55,8 @@ export class ServerTreeView {
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IThemeService private _themeService: IThemeService, @IThemeService private _themeService: IThemeService,
@IErrorMessageService private _errorMessageService: IErrorMessageService, @IErrorMessageService private _errorMessageService: IErrorMessageService,
@IConfigurationService private _configurationService: IConfigurationService @IConfigurationService private _configurationService: IConfigurationService,
@ICapabilitiesService capabilitiesService: ICapabilitiesService
) { ) {
this._activeConnectionsFilterAction = this._instantiationService.createInstance( this._activeConnectionsFilterAction = this._instantiationService.createInstance(
ActiveConnectionsFilterAction, ActiveConnectionsFilterAction,
@@ -64,6 +66,12 @@ export class ServerTreeView {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._onSelectionOrFocusChange = new Emitter(); this._onSelectionOrFocusChange = new Emitter();
this._actionProvider = this._instantiationService.createInstance(ServerTreeActionProvider); this._actionProvider = this._instantiationService.createInstance(ServerTreeActionProvider);
capabilitiesService.onCapabilitiesRegistered(() => {
if (this._connectionManagementService.hasRegisteredServers()) {
this.refreshTree();
this._treeSelectionHandler.onTreeActionStateChange(false);
}
});
} }
/** /**

View File

@@ -22,8 +22,8 @@ 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, NotebookEditorModel } from 'sql/parts/notebook/notebookInput'; import { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { INotebookService, INotebookEditor, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookService, INotebookEditor, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
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';
@@ -37,6 +37,7 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn
class MainThreadNotebookEditor extends Disposable { class MainThreadNotebookEditor extends Disposable {
private _contentChangedEmitter = new Emitter<NotebookContentChange>(); private _contentChangedEmitter = new Emitter<NotebookContentChange>();
public readonly contentChanged: Event<NotebookContentChange> = this._contentChangedEmitter.event; public readonly contentChanged: Event<NotebookContentChange> = this._contentChangedEmitter.event;
private _providerInfo: IProviderInfo;
constructor(public readonly editor: INotebookEditor) { constructor(public readonly editor: INotebookEditor) {
super(); super();
@@ -49,6 +50,9 @@ class MainThreadNotebookEditor extends Disposable {
this._contentChangedEmitter.fire(changeEvent); this._contentChangedEmitter.fire(changeEvent);
})); }));
}); });
editor.notebookParams.providerInfo.then(info => {
this._providerInfo = info;
});
} }
public get uri(): URI { public get uri(): URI {
@@ -64,11 +68,11 @@ class MainThreadNotebookEditor extends Disposable {
} }
public get providerId(): string { public get providerId(): string {
return this.editor.notebookParams.providerId; return this._providerInfo ? this._providerInfo.providerId : undefined;
} }
public get providers(): string[] { public get providers(): string[] {
return this.editor.notebookParams.providers; return this._providerInfo ? this._providerInfo.providers : [];
} }
public get cells(): ICellModel[] { public get cells(): ICellModel[] {

View File

@@ -85,11 +85,14 @@ export interface INotebookManager {
readonly serverManager: azdata.nb.ServerManager; readonly serverManager: azdata.nb.ServerManager;
} }
export interface IProviderInfo {
providerId: string;
providers: string[];
}
export interface INotebookParams extends IBootstrapParams { export interface INotebookParams extends IBootstrapParams {
notebookUri: URI; notebookUri: URI;
input: NotebookInput; input: NotebookInput;
providerId: string; providerInfo: Promise<IProviderInfo>;
providers: string[];
isTrusted: boolean; isTrusted: boolean;
profile?: IConnectionProfile; profile?: IConnectionProfile;
modelFactory?: ModelFactory; modelFactory?: ModelFactory;

View File

@@ -205,7 +205,7 @@ suite('SQL Connection Tree Action tests', () => {
return new TPromise((resolve) => resolve({})); return new TPromise((resolve) => resolve({}));
}); });
let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService);
serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString()));
serverTreeView.setup(x => x.refreshTree()); serverTreeView.setup(x => x.refreshTree());
let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object);
@@ -222,7 +222,7 @@ suite('SQL Connection Tree Action tests', () => {
return new TPromise((resolve) => resolve({})); return new TPromise((resolve) => resolve({}));
}); });
let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService);
serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString()));
serverTreeView.setup(x => x.refreshTree()); serverTreeView.setup(x => x.refreshTree());
let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object);
@@ -240,7 +240,7 @@ suite('SQL Connection Tree Action tests', () => {
return new TPromise((resolve) => resolve({})); return new TPromise((resolve) => resolve({}));
}); });
let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService);
serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString()));
serverTreeView.setup(x => x.refreshTree()); serverTreeView.setup(x => x.refreshTree());
let connectionTreeAction: RecentConnectionsFilterAction = new RecentConnectionsFilterAction(RecentConnectionsFilterAction.ID, RecentConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); let connectionTreeAction: RecentConnectionsFilterAction = new RecentConnectionsFilterAction(RecentConnectionsFilterAction.ID, RecentConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object);
@@ -257,7 +257,7 @@ suite('SQL Connection Tree Action tests', () => {
return new TPromise((resolve) => resolve({})); return new TPromise((resolve) => resolve({}));
}); });
let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined); let serverTreeView = TypeMoq.Mock.ofType(ServerTreeView, TypeMoq.MockBehavior.Strict, undefined, instantiationService.object, undefined, undefined, undefined, undefined, capabilitiesService);
serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString())); serverTreeView.setup(x => x.showFilteredTree(TypeMoq.It.isAnyString()));
serverTreeView.setup(x => x.refreshTree()); serverTreeView.setup(x => x.refreshTree());
let connectionTreeAction: RecentConnectionsFilterAction = new RecentConnectionsFilterAction(RecentConnectionsFilterAction.ID, RecentConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object); let connectionTreeAction: RecentConnectionsFilterAction = new RecentConnectionsFilterAction(RecentConnectionsFilterAction.ID, RecentConnectionsFilterAction.LABEL, serverTreeView.object, connectionManagementService.object);

View File

@@ -14,18 +14,21 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService';
suite('ServerTreeView onAddConnectionProfile handler tests', () => { suite('ServerTreeView onAddConnectionProfile handler tests', () => {
let serverTreeView: ServerTreeView; let serverTreeView: ServerTreeView;
let mockTree: TypeMoq.Mock<Tree>; let mockTree: TypeMoq.Mock<Tree>;
let mockRefreshTreeMethod: TypeMoq.Mock<Function>; let mockRefreshTreeMethod: TypeMoq.Mock<Function>;
let capabilitiesService = new CapabilitiesTestService();
setup(() => { setup(() => {
let instantiationService = new TestInstantiationService(); let instantiationService = new TestInstantiationService();
let mockConnectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Strict, {}, {}, new TestStorageService()); let mockConnectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Strict, {}, {}, new TestStorageService());
mockConnectionManagementService.setup(x => x.getConnectionGroups()).returns(x => []); mockConnectionManagementService.setup(x => x.getConnectionGroups()).returns(x => []);
serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, undefined, undefined, undefined); mockConnectionManagementService.setup(x => x.hasRegisteredServers()).returns(() => true);
serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, undefined, undefined, undefined, capabilitiesService);
let tree = <Tree>{ let tree = <Tree>{
clearSelection() { }, clearSelection() { },
getSelection() { }, getSelection() { },
@@ -91,4 +94,9 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => {
mockTree.verify(x => x.clearSelection(), TypeMoq.Times.never()); mockTree.verify(x => x.clearSelection(), TypeMoq.Times.never());
mockTree.verify(x => x.select(TypeMoq.It.isAny()), TypeMoq.Times.never()); mockTree.verify(x => x.select(TypeMoq.It.isAny()), TypeMoq.Times.never());
}); });
test('The tree refreshes when new capabilities are registered', () => {
capabilitiesService.fireCapabilitiesRegistered(undefined);
mockRefreshTreeMethod.verify(x => x(), TypeMoq.Times.once());
});
}); });

View File

@@ -138,6 +138,10 @@ export class CapabilitiesTestService implements ICapabilitiesService {
return Promise.resolve(null); return Promise.resolve(null);
} }
public fireCapabilitiesRegistered(providerFeatures: ProviderFeatures): void {
this._onCapabilitiesRegistered.fire(providerFeatures);
}
private _onCapabilitiesRegistered = new Emitter<ProviderFeatures>(); private _onCapabilitiesRegistered = new Emitter<ProviderFeatures>();
public readonly onCapabilitiesRegistered = this._onCapabilitiesRegistered.event; public readonly onCapabilitiesRegistered = this._onCapabilitiesRegistered.event;
} }

View File

@@ -203,38 +203,40 @@ export class MenubarControl extends Disposable {
} }
private detectAndRecommendCustomTitlebar(): void { private detectAndRecommendCustomTitlebar(): void {
if (!isLinux) { // {{SQL CARBON EDIT}} - Disable the custom titlebar recommendation
return; // if (!isLinux) {
} // return;
// }
if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) { // if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) {
if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') { // if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') {
// Issue will not arise for user, abort notification // // Issue will not arise for user, abort notification
return; // return;
} // }
const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar."); // const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar.");
this.notificationService.prompt(Severity.Info, message, [ // this.notificationService.prompt(Severity.Info, message, [
{ // {
label: nls.localize('goToSetting', "Open Settings"), // label: nls.localize('goToSetting', "Open Settings"),
run: () => { // run: () => {
return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); // return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' });
} // }
}, // },
{ // {
label: nls.localize('moreInfo', "More Info"), // label: nls.localize('moreInfo', "More Info"),
run: () => { // run: () => {
window.open('https://go.microsoft.com/fwlink/?linkid=2038566'); // window.open('https://go.microsoft.com/fwlink/?linkid=2038566');
} // }
}, // },
{ // {
label: nls.localize('neverShowAgain', "Don't Show Again"), // label: nls.localize('neverShowAgain', "Don't Show Again"),
run: () => { // run: () => {
this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL); // this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL);
} // }
} // }
]); // ]);
} // }
// {{SQL CARBON EDIT}} - End
} }
private registerListeners(): void { private registerListeners(): void {