Update SQL Bindings API (#18748)

* refactor addSqlBindingQuickpick so they can be called using sql-bindings api
This commit is contained in:
Barbara Valdez
2022-03-17 00:21:49 -07:00
committed by GitHub
parent 6fad30bf3c
commit 80a9d94648
4 changed files with 254 additions and 204 deletions

View File

@@ -4,10 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as utils from './utils';
import * as constants from './constants';
import { BindingType } from 'sql-bindings';
import * as path from 'path';
import { ConnectionDetails, IConnectionInfo } from 'vscode-mssql';
// https://github.com/microsoft/vscode-azurefunctions/blob/main/src/vscode-azurefunctions.api.d.ts
import { AzureFunctionsExtensionApi } from '../typings/vscode-azurefunctions.api';
// https://github.com/microsoft/vscode-azuretools/blob/main/ui/api.d.ts
@@ -307,3 +309,218 @@ export async function getAFProjectContainingFile(fileUri: vscode.Uri): Promise<v
export async function isFunctionProject(folderPath: string): Promise<boolean> {
return fs.existsSync(path.join(folderPath, constants.hostFileName));
}
/**
* Prompts the user to select type of binding and returns result
*/
export async function promptForBindingType(): Promise<(vscode.QuickPickItem & { type: BindingType }) | undefined> {
const inputOutputItems: (vscode.QuickPickItem & { type: BindingType })[] = [
{
label: constants.input,
type: BindingType.input
},
{
label: constants.output,
type: BindingType.output
}
];
const selectedBinding = (await vscode.window.showQuickPick(inputOutputItems, {
canPickMany: false,
title: constants.selectBindingType,
ignoreFocusOut: true
}));
return selectedBinding;
}
/**
* Prompts the user to enter object name for the SQL query
* @param bindingType Type of SQL Binding
*/
export async function promptForObjectName(bindingType: BindingType): Promise<string | undefined> {
return vscode.window.showInputBox({
prompt: bindingType === BindingType.input ? constants.sqlTableOrViewToQuery : constants.sqlTableToUpsert,
placeHolder: constants.placeHolderObject,
validateInput: input => input ? undefined : constants.nameMustNotBeEmpty,
ignoreFocusOut: true
});
}
/**
* Prompts the user to enter connection setting and updates it from AF project
* @param projectUri Azure Function project uri
*/
export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined): Promise<string | undefined> {
let connectionStringSettingName: string | undefined;
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
// show the settings from project's local.settings.json if there's an AF functions project
if (projectUri) {
let settings;
try {
settings = await getLocalSettingsJson(path.join(path.dirname(projectUri.fsPath!), constants.azureFunctionLocalSettingsFileName));
} catch (e) {
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
return;
}
let existingSettings: (vscode.QuickPickItem)[] = [];
if (settings?.Values) {
existingSettings = Object.keys(settings.Values).map(setting => {
return {
label: setting
} as vscode.QuickPickItem;
});
}
existingSettings.unshift({ label: constants.createNewLocalAppSettingWithIcon });
let sqlConnectionStringSettingExists = existingSettings.find(s => s.label === constants.sqlConnectionStringSetting);
while (!connectionStringSettingName) {
const selectedSetting = await vscode.window.showQuickPick(existingSettings, {
canPickMany: false,
title: constants.selectSetting,
ignoreFocusOut: true
});
if (!selectedSetting) {
// User cancelled
return;
}
if (selectedSetting.label === constants.createNewLocalAppSettingWithIcon) {
const newConnectionStringSettingName = await vscode.window.showInputBox(
{
title: constants.enterConnectionStringSettingName,
ignoreFocusOut: true,
value: sqlConnectionStringSettingExists ? '' : constants.sqlConnectionStringSetting,
validateInput: input => input ? undefined : constants.nameMustNotBeEmpty
}
) ?? '';
if (!newConnectionStringSettingName) {
// go back to select setting quickpick if user escapes from inputting the setting name in case they changed their mind
continue;
}
// show the connection string methods (user input and connection profile options)
const listOfConnectionStringMethods = [constants.connectionProfile, constants.userConnectionString];
while (true) {
const selectedConnectionStringMethod = await vscode.window.showQuickPick(listOfConnectionStringMethods, {
canPickMany: false,
title: constants.selectConnectionString,
ignoreFocusOut: true
});
if (!selectedConnectionStringMethod) {
// User cancelled
return;
}
let connectionString: string = '';
let includePassword: string | undefined;
let connectionInfo: IConnectionInfo | undefined;
let connectionDetails: ConnectionDetails;
if (selectedConnectionStringMethod === constants.userConnectionString) {
// User chooses to enter connection string manually
connectionString = await vscode.window.showInputBox(
{
title: constants.enterConnectionString,
ignoreFocusOut: true,
value: 'Server=localhost;Initial Catalog={db_name};User ID=sa;Password={your_password};Persist Security Info=False',
validateInput: input => input ? undefined : constants.valueMustNotBeEmpty
}
) ?? '';
} else {
// Let user choose from existing connections to create connection string from
connectionInfo = await vscodeMssqlApi.promptForConnection(true);
if (!connectionInfo) {
// User cancelled return to selectedConnectionStringMethod prompt
continue;
}
connectionDetails = { options: connectionInfo };
try {
// Prompt to include password in connection string if authentication type is SqlLogin and connection has password saved
if (connectionInfo.authenticationType === 'SqlLogin' && connectionInfo.password) {
includePassword = await vscode.window.showQuickPick([constants.yesString, constants.noString], {
title: constants.includePassword,
canPickMany: false,
ignoreFocusOut: true
});
if (includePassword === constants.yesString) {
// set connection string to include password
connectionString = await vscodeMssqlApi.getConnectionString(connectionDetails, true, false);
}
}
// set connection string to not include the password if connection info does not include password, or user chooses to not include password, or authentication type is not sql login
if (includePassword !== constants.yesString) {
connectionString = await vscodeMssqlApi.getConnectionString(connectionDetails, false, false);
}
} catch (e) {
// failed to get connection string for selected connection and will go back to prompt for connection string methods
console.warn(e);
void vscode.window.showErrorMessage(constants.failedToGetConnectionString);
continue;
}
}
if (connectionString) {
try {
const projectFolder: string = path.dirname(projectUri.fsPath);
const localSettingsPath: string = path.join(projectFolder, constants.azureFunctionLocalSettingsFileName);
let userPassword: string | undefined;
// Ask user to enter password if auth type is sql login and password is not saved
if (connectionInfo?.authenticationType === 'SqlLogin' && !connectionInfo?.password) {
userPassword = await vscode.window.showInputBox({
prompt: constants.enterPasswordPrompt,
placeHolder: constants.enterPasswordManually,
ignoreFocusOut: true,
password: true,
validateInput: input => input ? undefined : constants.valueMustNotBeEmpty
});
if (userPassword) {
// if user enters password replace password placeholder with user entered password
connectionString = connectionString.replace(constants.passwordPlaceholder, userPassword);
}
}
if (includePassword !== constants.yesString && !userPassword && connectionInfo?.authenticationType === 'SqlLogin') {
// if user does not want to include password or user does not enter password, show warning message that they will have to enter it manually later in local.settings.json
void vscode.window.showWarningMessage(constants.userPasswordLater, constants.openFile, constants.closeButton).then(async (result) => {
if (result === constants.openFile) {
// open local.settings.json file
void vscode.commands.executeCommand(constants.vscodeOpenCommand, vscode.Uri.file(localSettingsPath));
}
});
}
const success = await setLocalAppSetting(projectFolder, newConnectionStringSettingName, connectionString);
if (success) {
// exit both loops and insert binding
connectionStringSettingName = newConnectionStringSettingName;
break;
} else {
void vscode.window.showErrorMessage(constants.selectConnectionError());
}
} catch (e) {
// display error message and show select setting quickpick again
void vscode.window.showErrorMessage(constants.selectConnectionError(e));
continue;
}
}
}
} else {
// If user cancels out of this or doesn't want to overwrite an existing setting
// just return them to the select setting quickpick in case they changed their mind
connectionStringSettingName = selectedSetting.label;
}
}
// Add sql extension package reference to project. If the reference is already there, it doesn't get added again
await addNugetReferenceToProjectFile(projectUri.fsPath);
} else {
// if no AF project was found or there's more than one AF functions project in the workspace,
// ask for the user to input the setting name
connectionStringSettingName = await vscode.window.showInputBox({
prompt: constants.connectionStringSetting,
placeHolder: constants.connectionStringSettingPlaceholder,
ignoreFocusOut: true
});
}
return connectionStringSettingName;
}