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

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

@ -120,7 +120,7 @@ export default class ConnectionManager {
// send connection request message to service host // send connection request message to service host
let client: LanguageClient = SqlToolsServerClient.getInstance().getClient(); let client: LanguageClient = SqlToolsServerClient.getInstance().getClient();
client.sendRequest(ConnectionRequest.type, connectionDetails).then((result) => { 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 // 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._event.on(Constants.cmdDisconnect, () => { self.onDisconnect(); });
this.registerCommand(Constants.cmdRunQuery); this.registerCommand(Constants.cmdRunQuery);
this._event.on(Constants.cmdRunQuery, () => { self.onRunQuery(); }); 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 // Init status bar
this._statusview = new StatusView(); this._statusview = new StatusView();
@ -86,4 +90,14 @@ export default class MainController implements vscode.Disposable {
qr.onRunQuery(); 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 { public initialize(context: ExtensionContext): void {
// run the service host using dotnet.exe from the path // run the service host using dotnet.exe from the path
let serverCommand = 'dotnet.exe'; let serverCommand = 'dotnet';
let serverArgs = [ context.asAbsolutePath(path.join('tools', 'servicehost.dll')) ]; let serverArgs = [ context.asAbsolutePath(path.join('tools', 'microsoft.sqltools.servicehost.dll')) ];
let serverOptions: ServerOptions = { command: serverCommand, args: serverArgs, transport: TransportKind.stdio }; let serverOptions: ServerOptions = { command: serverCommand, args: serverArgs, transport: TransportKind.stdio };
// Options to control the language client // 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 cmdRunQuery = 'extension.runQuery';
export const cmdConnect = 'extension.connect'; export const cmdConnect = 'extension.connect';
export const cmdDisconnect = 'extension.disconnect'; export const cmdDisconnect = 'extension.disconnect';
export const cmdRegisterConnection = 'extension.registerconnection';
export const cmdUnregisterConnection = 'extension.unregisterconnection';
export const sqlDbPrefix = '.database.windows.net'; export const sqlDbPrefix = '.database.windows.net';
export const defaultConnectionTimeout = 15000; 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 msgNoConnectionsInSettings = 'To use this command, add connection information to VS Code User or Workspace settings.';
export const labelOpenGlobalSettings = 'Open Global Settings'; export const labelOpenGlobalSettings = 'Open Global Settings';
export const labelOpenWorkspaceSettings = 'Open Workspace Settings'; export const labelOpenWorkspaceSettings = 'Open Workspace Settings';
export const RegisterNewConnectionLabel = 'Register New Connection';
export const serverPrompt = 'Server name'; export const serverPrompt = 'Server name';
export const serverPlaceholder = 'hostname\\instance or <server>.database.windows.net'; export const serverPlaceholder = 'hostname\\instance or <server>.database.windows.net';

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

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

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

@ -18,9 +18,17 @@ export class RecentConnections {
label: ConnInfo.getPicklistLabel(item), label: ConnInfo.getPicklistLabel(item),
description: ConnInfo.getPicklistDescription(item), description: ConnInfo.getPicklistDescription(item),
detail: ConnInfo.getPicklistDetails(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); resolve(pickListItems);
}); });
}); });
@ -34,6 +42,7 @@ export class RecentConnections {
// Settings defined in workspace scope overwrite the settings defined in user scope // Settings defined in workspace scope overwrite the settings defined in user scope
let connections: Interfaces.IConnectionCredentials[] = []; let connections: Interfaces.IConnectionCredentials[] = [];
let config = vscode.workspace.getConfiguration(Constants.extensionName); let config = vscode.workspace.getConfiguration(Constants.extensionName);
let configValues = config[Constants.configMyConnections]; let configValues = config[Constants.configMyConnections];
for (let index = 0; index < configValues.length; index++) { for (let index = 0; index < configValues.length; index++) {
let element = configValues[index]; let element = configValues[index];

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

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