Support for dummy Register Connection command

- Fixed issue where service was called with "dotnet.exe" instead of "dotnet", which breaks Mac usage
- Added dummy commands for Register Connection and Unregister Connection
- Added the Register Connection dummy command as an option in the "Connect" command palette list. This will eventually enable users to connect without needing to manually edit the settings file
- Fixed issue where prompt for password didn't work as the prompt code was commented out
- Fixed issue where hitting ESC to cancel wasn't respected - it showed the "Retry" message box
- Refactored Connect code to make it easier to add multiple prompt for inputs in the workflow
This commit is contained in:
Kevin Cunnane 2016-07-27 18:35:39 -07:00
Родитель 189032e017
Коммит 5a163a599f
9 изменённых файлов: 182 добавлений и 62 удалений

Просмотреть файл

@ -95,6 +95,16 @@
"command": "extension.disconnect",
"title": "Disconnect active connection",
"category": "MSSQL"
},
{
"command": "extension.registerconnection",
"title": "Register SQL Connection",
"category": "MSSQL"
},
{
"command": "extension.unregisterconnection",
"title": "Unregister SQL Connection",
"category": "MSSQL"
}
],
"keybindings": [
@ -115,6 +125,12 @@
"key": "ctrl+shift+d",
"mac": "cmd+shift+d",
"when": "editorTextFocus && editorLangId == 'sql'"
},
{
"command": "extension.registerconnection",
"key": "ctrl+shift+r",
"mac": "cmd+shift+r",
"when": "editorTextFocus && editorLangId == 'sql'"
}
],
"configuration": {

Просмотреть файл

@ -120,7 +120,7 @@ export default class ConnectionManager {
// send connection request message to service host
let client: LanguageClient = SqlToolsServerClient.getInstance().getClient();
client.sendRequest(ConnectionRequest.type, connectionDetails).then((result) => {
// handle connection complete callbak
// handle connection complete callback
});
// legacy tedious connection until we fully move to service host
@ -140,4 +140,18 @@ export default class ConnectionManager {
});
});
}
public onRegisterConnection(): Promise<boolean> {
// const self = this;
return new Promise<any>((resolve, reject) => {
resolve(true);
});
}
public onUnregisterConnection(): Promise<boolean> {
// const self = this;
return new Promise<any>((resolve, reject) => {
resolve(true);
});
}
}

Просмотреть файл

@ -48,6 +48,10 @@ export default class MainController implements vscode.Disposable {
this._event.on(Constants.cmdDisconnect, () => { self.onDisconnect(); });
this.registerCommand(Constants.cmdRunQuery);
this._event.on(Constants.cmdRunQuery, () => { self.onRunQuery(); });
this.registerCommand(Constants.cmdRegisterConnection);
this._event.on(Constants.cmdRegisterConnection, () => { self.onRegisterConnection(); });
this.registerCommand(Constants.cmdUnregisterConnection);
this._event.on(Constants.cmdUnregisterConnection, () => { self.onUnregisterConnection(); });
// Init status bar
this._statusview = new StatusView();
@ -86,4 +90,14 @@ export default class MainController implements vscode.Disposable {
qr.onRunQuery();
}
}
// Prompts to register a new SQL connection for reuse across multiple connection
public onRegisterConnection(): Promise<boolean> {
return this._connectionMgr.onRegisterConnection();
}
// Prompts to remove a registered SQL connection
public onUnregisterConnection(): Promise<boolean> {
return this._connectionMgr.onUnregisterConnection();
}
}

Просмотреть файл

@ -34,8 +34,8 @@ export default class SqlToolsServiceClient {
public initialize(context: ExtensionContext): void {
// run the service host using dotnet.exe from the path
let serverCommand = 'dotnet.exe';
let serverArgs = [ context.asAbsolutePath(path.join('tools', 'servicehost.dll')) ];
let serverCommand = 'dotnet';
let serverArgs = [ context.asAbsolutePath(path.join('tools', 'microsoft.sqltools.servicehost.dll')) ];
let serverOptions: ServerOptions = { command: serverCommand, args: serverArgs, transport: TransportKind.stdio };
// Options to control the language client

Просмотреть файл

@ -0,0 +1,14 @@
'use strict';
import Interfaces = require('./interfaces');
// Concrete implementation of the IConnectionCredentials interface
export class ConnectionCredentials implements Interfaces.IConnectionCredentials {
server: string;
database: string;
user: string;
password: string;
connectionTimeout: number;
requestTimeout: number;
options: { encrypt: boolean, appName: string };
}

Просмотреть файл

@ -6,6 +6,8 @@ export const outputChannelName = 'MSSQL';
export const cmdRunQuery = 'extension.runQuery';
export const cmdConnect = 'extension.connect';
export const cmdDisconnect = 'extension.disconnect';
export const cmdRegisterConnection = 'extension.registerconnection';
export const cmdUnregisterConnection = 'extension.unregisterconnection';
export const sqlDbPrefix = '.database.windows.net';
export const defaultConnectionTimeout = 15000;
@ -59,6 +61,7 @@ export const recentConnectionsPlaceholder = 'Choose a connection from the list b
export const msgNoConnectionsInSettings = 'To use this command, add connection information to VS Code User or Workspace settings.';
export const labelOpenGlobalSettings = 'Open Global Settings';
export const labelOpenWorkspaceSettings = 'Open Workspace Settings';
export const RegisterNewConnectionLabel = 'Register New Connection';
export const serverPrompt = 'Server name';
export const serverPlaceholder = 'hostname\\instance or <server>.database.windows.net';

Просмотреть файл

@ -29,6 +29,7 @@ export interface IConnectionCredentials {
export interface IConnectionCredentialsQuickPickItem extends vscode.QuickPickItem {
connectionCreds: IConnectionCredentials;
isNewConnectionQuickPickItem: boolean;
};
// Obtained from an active connection to show in the status bar

Просмотреть файл

@ -18,9 +18,17 @@ export class RecentConnections {
label: ConnInfo.getPicklistLabel(item),
description: ConnInfo.getPicklistDescription(item),
detail: ConnInfo.getPicklistDetails(item),
connectionCreds: item
connectionCreds: item,
isNewConnectionQuickPickItem: false
};
});
// Always add an "Add New Connection" quickpick item
pickListItems.push(<Interfaces.IConnectionCredentialsQuickPickItem> {
label: Constants.RegisterNewConnectionLabel,
connectionCreds: undefined,
isNewConnectionQuickPickItem: true
});
resolve(pickListItems);
});
});
@ -34,6 +42,7 @@ export class RecentConnections {
// Settings defined in workspace scope overwrite the settings defined in user scope
let connections: Interfaces.IConnectionCredentials[] = [];
let config = vscode.workspace.getConfiguration(Constants.extensionName);
let configValues = config[Constants.configMyConnections];
for (let index = 0; index < configValues.length; index++) {
let element = configValues[index];

Просмотреть файл

@ -2,19 +2,20 @@
import vscode = require('vscode');
import Constants = require('../models/constants');
import { RecentConnections } from '../models/recentConnections';
import Interfaces = require('../models/interfaces');
import { ConnectionCredentials } from '../models/connectionCredentials';
import { IConnectionCredentials, IConnectionCredentialsQuickPickItem } from '../models/interfaces';
let async = require('async');
export class ConnectionUI {
// Helper to let user choose a connection from a picklist
// Return the ConnectionInfo for the user's choice
public showConnections(): Promise<Interfaces.IConnectionCredentials> {
public showConnections(): Promise<IConnectionCredentials> {
const self = this;
return new Promise<Interfaces.IConnectionCredentials>((resolve, reject) => {
return new Promise<IConnectionCredentials>((resolve, reject) => {
let recentConnections = new RecentConnections();
recentConnections.getPickListItems()
.then((picklist: Interfaces.IConnectionCredentialsQuickPickItem[]) => {
.then((picklist: IConnectionCredentialsQuickPickItem[]) => {
if (picklist.length === 0) {
// No recent connections - prompt to open user settings or workspace settings to add a connection
self.openUserOrWorkspaceSettings();
@ -56,9 +57,9 @@ export class ConnectionUI {
}
// Helper to let user choose a connection from a picklist
private showConnectionsPickList(pickList: Interfaces.IConnectionCredentialsQuickPickItem[]): Promise<Interfaces.IConnectionCredentials> {
private showConnectionsPickList(pickList: IConnectionCredentialsQuickPickItem[]): Promise<IConnectionCredentials> {
const self = this;
return new Promise<Interfaces.IConnectionCredentials>((resolve, reject) => {
return new Promise<IConnectionCredentials>((resolve, reject) => {
// init picklist options
let opts: vscode.QuickPickOptions = {
matchOnDescription: true,
@ -69,23 +70,56 @@ export class ConnectionUI {
vscode.window.showQuickPick(pickList, opts)
.then(selection => {
if (selection !== undefined) {
// user chose a connection from picklist. Prompt for mandatory info that's missing (e.g. username and/or password)
let connectionCreds = selection.connectionCreds;
self.promptForMissingInfo(connectionCreds).then((resolvedConnectionCreds) => {
let connectFunc: Promise<IConnectionCredentials>;
if (selection.isNewConnectionQuickPickItem) {
// call the workflow to create a new connection
connectFunc = self.promptForRegisterConnection();
} else {
// user chose a connection from picklist. Prompt for mandatory info that's missing (e.g. username and/or password)
let connectionCreds = selection.connectionCreds;
connectFunc = self.promptForMissingInfo(connectionCreds);
}
connectFunc.then((resolvedConnectionCreds) => {
if (!resolvedConnectionCreds) {
return false;
}
resolve(resolvedConnectionCreds);
});
}
});
});
}
// Prompt user for missing details in the given IConnectionCredentials
private promptForMissingInfo(connectionCreds: Interfaces.IConnectionCredentials): Promise<Interfaces.IConnectionCredentials> {
private promptForRegisterConnection(): Promise<IConnectionCredentials> {
const self = this;
return new Promise<Interfaces.IConnectionCredentials>((resolve, reject) => {
return new Promise<IConnectionCredentials>((resolve, reject) => {
// called by async.js when all functions have finished executing
let final = function(err, object, results): boolean {
if (err) {
return false;
} else {
resolve(results); // final connectionCreds with all the missing inputs filled in
}
};
let connectionCreds: IConnectionCredentials = new ConnectionCredentials();
// call each of these functions in a waterfall and pass parameters from one to the next
// See this for more info: https://github.com/caolan/async#waterfall
async.waterfall([
async.apply(self.promptForServer, self, connectionCreds),
self.promptForUsername,
self.promptForPassword
], final);
});
}
// Prompt user for missing details in the given IConnectionCredentials
private promptForMissingInfo(connectionCreds: IConnectionCredentials): Promise<IConnectionCredentials> {
const self = this;
return new Promise<IConnectionCredentials>((resolve, reject) => {
// called by async.js when all functions have finished executing
let final = function(err, object, results): boolean {
if (err) {
@ -105,38 +139,48 @@ export class ConnectionUI {
}
// Helper to prompt for username
private promptForUsername(self, connectionCreds: Interfaces.IConnectionCredentials, callback): void {
if (connectionCreds.user) {
// we already have a username - tell async.js to proceed to the next function
callback(undefined, self, connectionCreds);
} else {
// we don't have a username, prompt the user to enter it
let usernameInputOptions: vscode.InputBoxOptions = {placeHolder: Constants.usernamePlaceholder, prompt: Constants.usernamePrompt};
self.promptUser(usernameInputOptions)
.then((input) => {
if (input) {
connectionCreds.user = input;
callback(undefined, self, connectionCreds); // tell async.js to proceed to the next function
} else {
// user cancelled - raise an error and abort the wizard
callback(true, self, connectionCreds);
}
});
}
private promptForServer(self: ConnectionUI, connectionCreds: IConnectionCredentials, callback): void {
let inputOptions: vscode.InputBoxOptions = {placeHolder: Constants.serverPlaceholder, prompt: Constants.serverPrompt};
self.promptForValue(self, connectionCreds, inputOptions, (c) => self.isNotEmpty(connectionCreds.server), (c, input) => c.server = input, callback);
}
// Helper to prompt for username
private promptForUsername(self: ConnectionUI, connectionCreds: IConnectionCredentials, callback): void {
let usernameInputOptions: vscode.InputBoxOptions = {placeHolder: Constants.usernamePlaceholder, prompt: Constants.usernamePrompt};
self.promptForValue(self, connectionCreds, usernameInputOptions, (c) => self.isNotEmpty(connectionCreds.user), (c, input) => c.user = input, callback);
}
// Helper to prompt for password
private promptForPassword(self, connectionCreds: Interfaces.IConnectionCredentials, callback): void {
if (connectionCreds.password) {
// we already have a password - tell async.js to proceed to the next function
private promptForPassword(self: ConnectionUI, connectionCreds: IConnectionCredentials, callback): void {
let passwordInputOptions: vscode.InputBoxOptions = {placeHolder: Constants.passwordPlaceholder, prompt: Constants.passwordPrompt, password: true};
self.promptForValue(self, connectionCreds, passwordInputOptions,
(c) => self.isNotEmpty(connectionCreds.password), (c, input) => c.password = input, callback);
}
private isNotEmpty(str: string): boolean {
return (str && 0 !== str.length);
}
// Helper function that checks for any property on a credential object, and if missing prompts
// the user to enter it. Handles cancelation by returning true for the err parameter
private promptForValue(
self: ConnectionUI,
connectionCreds: IConnectionCredentials,
inputOptions: vscode.InputBoxOptions,
valueChecker: (c: IConnectionCredentials) => boolean,
valueSetter: (c: IConnectionCredentials, input: any) => void,
callback): void {
if (valueChecker(connectionCreds)) {
// we already have the required value - tell async.js to proceed to the next function
callback(undefined, self, connectionCreds);
} else {
// we don't have a password, prompt the user to enter it
let passwordInputOptions: vscode.InputBoxOptions = {placeHolder: Constants.passwordPlaceholder, prompt: Constants.passwordPrompt, password: true};
self.promptUser(passwordInputOptions)
// we don't have the value, prompt the user to enter it
self.promptUser(inputOptions)
.then((input) => {
if (input) {
connectionCreds.password = input;
valueSetter(connectionCreds, input);
callback(undefined, self, connectionCreds); // tell async.js to proceed to the next function
} else {
// user cancelled - raise an error and abort the wizard
@ -147,25 +191,30 @@ export class ConnectionUI {
}
// Helper to prompt user for input
// If the input is a mandatory inout then keeps prompting the user until cancelled
// private promptUser(options: vscode.InputBoxOptions, mandatoryInput = true): Promise<string> {
// return new Promise<string>((resolve, reject) => {
// let prompt = () => {
// vscode.window.showInputBox(options).then((input) => {
// if ((!input || !input.trim()) && mandatoryInput) {
// // Prompt user to re-enter if this is a mandatory input
// vscode.window.showWarningMessage(options.prompt + Constants.gMsgIsRequired, Constants.gMsgRetry).then((choice) => {
// if (choice === Constants.gMsgRetry) {
// prompt();
// }
// });
// return false;
// } else {
// resolve(input);
// }
// });
// };
// prompt();
// });
// }
// If the input is a mandatory input then keeps prompting the user until cancelled
private promptUser(options: vscode.InputBoxOptions, mandatoryInput = true): Promise<string> {
return new Promise<string>((resolve, reject) => {
let prompt = () => {
vscode.window.showInputBox(options).then((input) => {
if (input === undefined) {
// The return value is undefined if the message was canceled.
// need to separate from empty string (which means it might be required)
return false;
}
if ((!input || !input.trim()) && mandatoryInput) {
// Prompt user to re-enter if this is a mandatory input
vscode.window.showWarningMessage(options.prompt + Constants.msgIsRequired, Constants.msgRetry).then((choice) => {
if (choice === Constants.msgRetry) {
prompt();
}
});
return false;
} else {
resolve(input);
}
});
};
prompt();
});
}
}