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:
Родитель
189032e017
Коммит
5a163a599f
16
package.json
16
package.json
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче