mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 09:35:40 -05:00
Add Table Menu for SQL Bindings promptForObjectName (#19358)
* first wip for table menu prompt * fix up some table prompts * use simpleExecuteRequest * fix table prompt scenario for sql binding also * fix/add more testing and address comments * add brackets to selected database * add manually entered option
This commit is contained in:
@@ -7,7 +7,8 @@ import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as utils from './utils';
|
||||
import * as constants from './constants';
|
||||
import { BindingType } from 'sql-bindings';
|
||||
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
|
||||
import { BindingType, IConnectionStringInfo } from 'sql-bindings';
|
||||
import { ConnectionDetails, IConnectionInfo } from 'vscode-mssql';
|
||||
// https://github.com/microsoft/vscode-azurefunctions/blob/main/src/vscode-azurefunctions.api.d.ts
|
||||
import { AzureFunctionsExtensionApi } from '../../../types/vscode-azurefunctions.api';
|
||||
@@ -313,23 +314,54 @@ export async function promptForBindingType(funcName?: string): Promise<BindingTy
|
||||
/**
|
||||
* Prompts the user to enter object name for the SQL query
|
||||
* @param bindingType Type of SQL Binding
|
||||
* @param connectionInfo (optional) connection info from the selected connection profile
|
||||
* if left undefined we prompt to manually enter the object name
|
||||
* @returns the object name from user's input or menu choice
|
||||
*/
|
||||
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
|
||||
});
|
||||
export async function promptForObjectName(bindingType: BindingType, connectionInfo?: IConnectionInfo): Promise<string | undefined> {
|
||||
// show the connection string methods (user input and connection profile options)
|
||||
let connectionURI: string | undefined;
|
||||
let selectedDatabase: string | undefined;
|
||||
|
||||
while (true) {
|
||||
if (!connectionInfo) {
|
||||
// prompt is shown when user selects an existing connection string setting
|
||||
// or manually enters a connection string
|
||||
return promptToManuallyEnterObjectName(bindingType);
|
||||
}
|
||||
|
||||
// TODO create path to solve for selectView (first need to support views as well)
|
||||
// Prompt user to select a table based on connection profile and selected database}
|
||||
// get connectionURI and selectedDatabase to be used for listing tables query request
|
||||
connectionURI = await getConnectionURI(connectionInfo);
|
||||
if (!connectionURI) {
|
||||
// User cancelled or mssql connection error
|
||||
// we will then prompt user to choose a connection profile again
|
||||
continue;
|
||||
}
|
||||
selectedDatabase = await promptSelectDatabase(connectionURI);
|
||||
if (!selectedDatabase) {
|
||||
// User cancelled
|
||||
// we will then prompt user to choose a connection profile again
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionInfo.database = selectedDatabase;
|
||||
|
||||
let selectedObjectName = await promptSelectTable(connectionURI, bindingType, selectedDatabase);
|
||||
|
||||
return selectedObjectName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user to enter connection setting and updates it from AF project
|
||||
* @param projectUri Azure Function project uri
|
||||
* @param connectionInfo connection info from the user to update the connection string
|
||||
* @param connectionInfo (optional) connection info from the user to update the connection string,
|
||||
* if left undefined we prompt the user for the connection info
|
||||
* @returns connection string setting name to be used for the createFunction API
|
||||
*/
|
||||
export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<string | undefined> {
|
||||
export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<IConnectionStringInfo | undefined> {
|
||||
let connectionStringSettingName: string | undefined;
|
||||
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
||||
|
||||
@@ -459,6 +491,11 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
|
||||
validateInput: input => input ? undefined : constants.valueMustNotBeEmpty
|
||||
}
|
||||
) ?? '';
|
||||
if (!connectionString) {
|
||||
// User cancelled
|
||||
// we can prompt for connection string methods again
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Let user choose from existing connections to create connection string from
|
||||
connectionInfo = await vscodeMssqlApi.promptForConnection(true);
|
||||
@@ -509,7 +546,7 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
}
|
||||
return connectionStringSettingName;
|
||||
return { connectionStringSettingName: connectionStringSettingName!, connectionInfo: connectionInfo };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,10 +616,9 @@ export async function promptConnectionStringPasswordAndUpdateConnectionString(co
|
||||
}
|
||||
}
|
||||
|
||||
export async function promptSelectDatabase(connectionInfo: IConnectionInfo): Promise<string | undefined> {
|
||||
export async function promptSelectDatabase(connectionURI: string): Promise<string | undefined> {
|
||||
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
||||
|
||||
let connectionURI = await vscodeMssqlApi.connect(connectionInfo);
|
||||
let listDatabases = await vscodeMssqlApi.listDatabases(connectionURI);
|
||||
const selectedDatabase = (await vscode.window.showQuickPick(listDatabases, {
|
||||
canPickMany: false,
|
||||
@@ -596,3 +632,77 @@ export async function promptSelectDatabase(connectionInfo: IConnectionInfo): Pro
|
||||
}
|
||||
return selectedDatabase;
|
||||
}
|
||||
|
||||
export async function getConnectionURI(connectionInfo: IConnectionInfo): Promise<string | undefined> {
|
||||
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
||||
|
||||
let connectionURI: string = '';
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: constants.connectionProgressTitle,
|
||||
cancellable: false
|
||||
}, async (_progress, _token) => {
|
||||
// show progress bar while connecting to the users selected connection profile
|
||||
connectionURI = await vscodeMssqlApi.connect(connectionInfo!);
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
// mssql connection error will be shown to the user
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return connectionURI;
|
||||
}
|
||||
|
||||
export async function promptSelectTable(connectionURI: string, bindingType: BindingType, selectedDatabase: string): Promise<string | undefined> {
|
||||
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
||||
const userObjectName = bindingType === BindingType.input ? constants.enterObjectName : constants.enterObjectNameToUpsert;
|
||||
|
||||
// Create query to get list of tables from database selected
|
||||
let tableQuery = tablesQuery(selectedDatabase);
|
||||
const params = { ownerUri: connectionURI, queryString: tableQuery };
|
||||
// send SimpleExecuteRequest query to STS to get list of schema and tables based on the connection profile of the user
|
||||
let queryResult: azureFunctionsContracts.SimpleExecuteResult = await vscodeMssqlApi.sendRequest(azureFunctionsContracts.SimpleExecuteRequest.type, params);
|
||||
|
||||
// Get schema and table names from query result rows
|
||||
const tableNames = queryResult.rows.map(r => r[0].displayValue);
|
||||
// add manual entry option to table names list for user to choose from as well (with pencil icon)
|
||||
let manuallyEnterObjectName = '$(pencil) ' + userObjectName;
|
||||
tableNames.unshift(manuallyEnterObjectName);
|
||||
// prompt user to select table from list of tables options
|
||||
while (true) {
|
||||
let selectedObject = await vscode.window.showQuickPick(tableNames, {
|
||||
canPickMany: false,
|
||||
title: constants.selectTable,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (selectedObject === manuallyEnterObjectName) {
|
||||
let selectedObject = promptToManuallyEnterObjectName(bindingType);
|
||||
if (!selectedObject) {
|
||||
// user cancelled so we will show the tables prompt again
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return selectedObject;
|
||||
}
|
||||
}
|
||||
|
||||
export function tablesQuery(selectedDatabase: string): string {
|
||||
let quotedDatabase = '[' + utils.escapeClosingBrackets(selectedDatabase) + ']';
|
||||
return `SELECT CONCAT(QUOTENAME(table_schema),'.',QUOTENAME(table_name)) from ${quotedDatabase}.INFORMATION_SCHEMA.TABLES where TABLE_TYPE = 'BASE TABLE'`;
|
||||
}
|
||||
|
||||
export async function promptToManuallyEnterObjectName(bindingType: BindingType): Promise<string | undefined> {
|
||||
// user manually enters table or view to query or upsert into
|
||||
let selectedObject = await vscode.window.showInputBox({
|
||||
prompt: bindingType === BindingType.input ? constants.sqlTableOrViewToQuery : constants.sqlTableToUpsert,
|
||||
placeHolder: constants.placeHolderObject,
|
||||
validateInput: input => input ? undefined : constants.nameMustNotBeEmpty,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
return selectedObject;
|
||||
}
|
||||
@@ -75,6 +75,9 @@ export const userPasswordLater = localize('userPasswordLater', 'In order to user
|
||||
export const openFile = localize('openFile', "Open File");
|
||||
export const closeButton = localize('closeButton', "Close");
|
||||
export const connectionProgressTitle = localize('connectionProgressTitle', "Testing SQL Server connection...");
|
||||
export const enterObjectName = localize('enterObjectName', 'Enter SQL table or view to query');
|
||||
export const enterObjectNameToUpsert = localize('enterObjectNameToUpsert', 'Enter SQL table to upsert into');
|
||||
export const selectTable = localize('selectTable', 'Select table to use');
|
||||
export const selectConnectionError = (err?: any): string => err ? localize('selectConnectionError', "Failed to set connection string app setting: {0}", utils.getErrorMessage(err)) : localize('unableToSetConnectionString', "Failed to set connection string app setting");
|
||||
export function selectBindingType(funcName?: string): string { return funcName ? localize('selectBindingTypeToSpecifiedFunction', "Select type of binding for the function '{0}'", funcName) : localize('selectBindingType', "Select type of binding"); }
|
||||
export function settingAlreadyExists(settingName: string): string { return localize('SettingAlreadyExists', 'Local app setting \'{0}\' already exists. Overwrite?', settingName); }
|
||||
|
||||
@@ -173,4 +173,3 @@ export function getErrorType(error: any): string | undefined {
|
||||
return 'UnknownError';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,3 +19,69 @@ export namespace AddSqlBindingRequest {
|
||||
export namespace GetAzureFunctionsRequest {
|
||||
export const type = new RequestType<GetAzureFunctionsParams, GetAzureFunctionsResult, void, void>('azureFunctions/getAzureFunctions');
|
||||
}
|
||||
|
||||
// ------------------------------- < Execute String > ------------------------------------
|
||||
|
||||
// source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L1021
|
||||
export interface SimpleExecuteParams {
|
||||
queryString: string;
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Query Execute Result will return rowCount, columnInfo, and rows from STS request
|
||||
* source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L1026
|
||||
* rowCount is the number of rows returned with resultset
|
||||
* columnInfo is the details about the columns that are povided as solutions
|
||||
* rows is a 2D array of the cell values from the resultset
|
||||
*/
|
||||
export interface SimpleExecuteResult {
|
||||
rowCount: number;
|
||||
columnInfo: IDbColumn[];
|
||||
rows: DbCellValue[][];
|
||||
}
|
||||
|
||||
// source: https://github.com/microsoft/sqlops-dataprotocolclient/blob/main/src/protocol.ts#L437
|
||||
export namespace SimpleExecuteRequest {
|
||||
export const type = new RequestType<SimpleExecuteParams, SimpleExecuteResult, void, void>('query/simpleexecute');
|
||||
}
|
||||
|
||||
// source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L907
|
||||
export interface IDbColumn {
|
||||
allowDBNull?: boolean | undefined;
|
||||
baseCatalogName: string;
|
||||
baseColumnName: string;
|
||||
baseSchemaName: string;
|
||||
baseServerName: string;
|
||||
baseTableName: string;
|
||||
columnName: string;
|
||||
columnOrdinal?: number | undefined;
|
||||
columnSize?: number | undefined;
|
||||
isAliased?: boolean | undefined;
|
||||
isAutoIncrement?: boolean | undefined;
|
||||
isExpression?: boolean | undefined;
|
||||
isHidden?: boolean | undefined;
|
||||
isIdentity?: boolean | undefined;
|
||||
isKey?: boolean | undefined;
|
||||
isBytes?: boolean | undefined;
|
||||
isChars?: boolean | undefined;
|
||||
isSqlVariant?: boolean | undefined;
|
||||
isUdt?: boolean | undefined;
|
||||
dataType: string;
|
||||
isXml?: boolean | undefined;
|
||||
isJson?: boolean | undefined;
|
||||
isLong?: boolean | undefined;
|
||||
isReadOnly?: boolean | undefined;
|
||||
isUnique?: boolean | undefined;
|
||||
numericPrecision?: number | undefined;
|
||||
numericScale?: number | undefined;
|
||||
udtAssemblyQualifiedName: string;
|
||||
dataTypeName: string;
|
||||
}
|
||||
|
||||
// source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L1066
|
||||
export interface DbCellValue {
|
||||
displayValue: string;
|
||||
isNull: boolean;
|
||||
invariantCultureDisplayValue: string;
|
||||
}
|
||||
@@ -77,17 +77,7 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getBindingType)
|
||||
.withAdditionalProperties(propertyBag).send();
|
||||
|
||||
// 3. ask for object name for the binding
|
||||
quickPickStep = 'getObjectName';
|
||||
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding);
|
||||
|
||||
if (!objectName) {
|
||||
return;
|
||||
}
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getObjectName)
|
||||
.withAdditionalProperties(propertyBag).send();
|
||||
|
||||
// 4. ask for connection string setting name
|
||||
// 3. ask for connection string setting name
|
||||
let projectUri: vscode.Uri | undefined;
|
||||
try {
|
||||
projectUri = await azureFunctionsUtils.getAFProjectContainingFile(uri);
|
||||
@@ -96,17 +86,26 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
|
||||
}
|
||||
|
||||
quickPickStep = 'updateConnectionString';
|
||||
let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri);
|
||||
if (!connectionStringSettingName) {
|
||||
let connectionStringInfo = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri);
|
||||
if (!connectionStringInfo) {
|
||||
return;
|
||||
}
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.updateConnectionString)
|
||||
.withAdditionalProperties(propertyBag).send();
|
||||
|
||||
// 4. ask for object name for the binding
|
||||
quickPickStep = 'getObjectName';
|
||||
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding, connectionStringInfo.connectionInfo);
|
||||
if (!objectName) {
|
||||
return;
|
||||
}
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getObjectName)
|
||||
.withAdditionalProperties(propertyBag).send();
|
||||
|
||||
// 5. insert binding
|
||||
try {
|
||||
quickPickStep = 'insertBinding';
|
||||
const result = await addSqlBinding(selectedBinding, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName);
|
||||
const result = await addSqlBinding(selectedBinding, uri.fsPath, azureFunctionName, objectName, connectionStringInfo.connectionStringSettingName!);
|
||||
|
||||
if (!result.success) {
|
||||
void vscode.window.showErrorMessage(result.errorMessage);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import { ITreeNodeInfo } from 'vscode-mssql';
|
||||
import { IExtension, BindingType, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
|
||||
import { IConnectionInfo, ITreeNodeInfo } from 'vscode-mssql';
|
||||
import { IExtension, BindingType, GetAzureFunctionsResult, ResultStatus, IConnectionStringInfo } from 'sql-bindings';
|
||||
import { addSqlBinding, createAzureFunction, getAzureFunctions } from './services/azureFunctionsService';
|
||||
import { launchAddSqlBindingQuickpick } from './dialogs/addSqlBindingQuickpick';
|
||||
import { promptForBindingType, promptAndUpdateConnectionStringSetting, promptForObjectName } from './common/azureFunctionsUtils';
|
||||
@@ -23,11 +23,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
|
||||
promptForBindingType: async (funcName?: string): Promise<BindingType | undefined> => {
|
||||
return promptForBindingType(funcName);
|
||||
},
|
||||
promptForObjectName: async (bindingType: BindingType): Promise<string | undefined> => {
|
||||
return promptForObjectName(bindingType);
|
||||
promptForObjectName: async (bindingType: BindingType, connectionInfo?: IConnectionInfo): Promise<string | undefined> => {
|
||||
return promptForObjectName(bindingType, connectionInfo);
|
||||
},
|
||||
promptAndUpdateConnectionStringSetting: async (projectUri: vscode.Uri | undefined): Promise<string | undefined> => {
|
||||
return promptAndUpdateConnectionStringSetting(projectUri);
|
||||
promptAndUpdateConnectionStringSetting: async (projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<IConnectionStringInfo | undefined> => {
|
||||
return promptAndUpdateConnectionStringSetting(projectUri, connectionInfo);
|
||||
},
|
||||
getAzureFunctions: async (filePath: string): Promise<GetAzureFunctionsResult> => {
|
||||
return getAzureFunctions(filePath);
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
|
||||
import * as constants from '../common/constants';
|
||||
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
|
||||
import { CreateAzureFunctionStep, TelemetryActions, TelemetryReporter, TelemetryViews, ExitReason } from '../common/telemetry';
|
||||
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
|
||||
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, IConnectionStringInfo, ResultStatus } from 'sql-bindings';
|
||||
import { IConnectionInfo, ITreeNodeInfo } from 'vscode-mssql';
|
||||
import { createAddConnectionStringStep } from '../createNewProject/addConnectionStringStep';
|
||||
|
||||
@@ -83,6 +83,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
||||
return;
|
||||
}
|
||||
projectFolder = projectFolders[0].fsPath;
|
||||
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||
.withAdditionalProperties(propertyBag).send();
|
||||
break;
|
||||
@@ -125,45 +126,13 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
||||
}
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||
.withAdditionalProperties(propertyBag).withConnectionInfo(connectionInfo).send();
|
||||
telemetryStep = CreateAzureFunctionStep.getConnectionProfile;
|
||||
let connectionURI: string = '';
|
||||
try {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: constants.connectionProgressTitle,
|
||||
cancellable: false
|
||||
}, async (_progress, _token) => {
|
||||
// list databases based on connection profile selected
|
||||
connectionURI = await vscodeMssqlApi.connect(connectionInfo!);
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
// mssql connection error will be shown to the user
|
||||
// we will then prompt user to choose a connection profile again
|
||||
continue;
|
||||
}
|
||||
// list databases based on connection profile selected
|
||||
telemetryStep = CreateAzureFunctionStep.getDatabase;
|
||||
let listDatabases = await vscodeMssqlApi.listDatabases(connectionURI);
|
||||
const selectedDatabase = (await vscode.window.showQuickPick(listDatabases, {
|
||||
canPickMany: false,
|
||||
title: constants.selectDatabase,
|
||||
ignoreFocusOut: true
|
||||
}));
|
||||
|
||||
if (!selectedDatabase) {
|
||||
// User cancelled
|
||||
// we will then prompt user to choose a connection profile again
|
||||
continue;
|
||||
}
|
||||
connectionInfo.database = selectedDatabase;
|
||||
telemetryStep = CreateAzureFunctionStep.getObjectName;
|
||||
|
||||
// prompt user for object name to create function from
|
||||
objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding);
|
||||
objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding, connectionInfo);
|
||||
if (!objectName) {
|
||||
// user cancelled
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -213,16 +182,20 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
||||
let templateId: string = selectedBindingType === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID;
|
||||
|
||||
// prompt for Connection String Setting Name
|
||||
let connectionStringSettingName: string | undefined = constants.sqlConnectionStringSetting;
|
||||
let connectionStringInfo: IConnectionStringInfo | undefined = { connectionStringSettingName: constants.sqlConnectionStringSetting, connectionInfo: connectionInfo };
|
||||
if (!isCreateNewProject && projectFile) {
|
||||
telemetryStep = CreateAzureFunctionStep.getConnectionStringSettingName;
|
||||
connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
|
||||
connectionStringInfo = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
|
||||
if (!connectionStringInfo) {
|
||||
// User cancelled connection string setting name prompt or connection string method prompt
|
||||
return;
|
||||
}
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||
.withAdditionalProperties(propertyBag)
|
||||
.withConnectionInfo(connectionInfo).send();
|
||||
}
|
||||
// addtional execution step that will be used by vscode-azurefunctions to execute only when creating a new azure function project
|
||||
let connectionStringExecuteStep = createAddConnectionStringStep(projectFolder, connectionInfo, connectionStringSettingName);
|
||||
let connectionStringExecuteStep = createAddConnectionStringStep(projectFolder, connectionInfo, connectionStringInfo.connectionStringSettingName);
|
||||
|
||||
// create C# Azure Function with SQL Binding
|
||||
telemetryStep = 'createFunctionAPI';
|
||||
@@ -233,7 +206,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
||||
templateId: templateId,
|
||||
functionName: functionName,
|
||||
functionSettings: {
|
||||
connectionStringSetting: connectionStringSettingName,
|
||||
connectionStringSetting: connectionStringInfo.connectionStringSettingName,
|
||||
...(selectedBindingType === BindingType.input && { object: objectName }),
|
||||
...(selectedBindingType === BindingType.output && { table: objectName })
|
||||
},
|
||||
|
||||
21
extensions/sql-bindings/src/sql-bindings.d.ts
vendored
21
extensions/sql-bindings/src/sql-bindings.d.ts
vendored
@@ -7,6 +7,7 @@
|
||||
declare module 'sql-bindings' {
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { IConnectionInfo } from 'vscode-mssql';
|
||||
|
||||
export const enum extension {
|
||||
name = 'Microsoft.sql-bindings',
|
||||
@@ -36,14 +37,20 @@ declare module 'sql-bindings' {
|
||||
/**
|
||||
* Prompts the user to enter object name for the SQL query
|
||||
* @param bindingType Type of SQL Binding
|
||||
* @param connectionInfo (optional) connection info from the selected connection profile
|
||||
* if left undefined we prompt to manually enter the object name
|
||||
* @returns the object name from user's input or menu choice
|
||||
*/
|
||||
promptForObjectName(bindingType: BindingType): Promise<string | undefined>;
|
||||
promptForObjectName(bindingType: BindingType, connectionInfo?: IConnectionInfo): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Prompts the user to enter connection setting and updates it from AF project
|
||||
* @param projectUri Azure Function project uri
|
||||
*/
|
||||
promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined): Promise<string | undefined>;
|
||||
* @param connectionInfo (optional) connection info from the user to update the connection string,
|
||||
* if left undefined we prompt the user for the connection info
|
||||
* @returns connection string setting name to be used for the createFunction API
|
||||
*/
|
||||
promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<IConnectionStringInfo | undefined>;
|
||||
|
||||
/**
|
||||
* Gets the names of the Azure Functions in the file
|
||||
@@ -119,4 +126,12 @@ declare module 'sql-bindings' {
|
||||
azureFunctions: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Result from promptAndUpdateConnectionStringSetting
|
||||
*/
|
||||
export interface IConnectionStringInfo {
|
||||
connectionStringSettingName: string;
|
||||
connectionInfo: IConnectionInfo | undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,14 +7,17 @@ import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as fs from 'fs';
|
||||
import * as utils from '../../common/utils';
|
||||
import * as constants from '../../common/constants';
|
||||
import * as azureFunctionUtils from '../../common/azureFunctionsUtils';
|
||||
import * as azureFunctionService from '../../services/azureFunctionsService';
|
||||
import * as azureFunctionsContracts from '../../contracts/azureFunctions/azureFunctionsContracts';
|
||||
|
||||
import { createTestUtils, TestUtils, createTestCredentials } from '../testUtils';
|
||||
import { launchAddSqlBindingQuickpick } from '../../dialogs/addSqlBindingQuickpick';
|
||||
import { BindingType } from 'sql-bindings';
|
||||
import { IConnectionInfo } from 'vscode-mssql';
|
||||
|
||||
let testUtils: TestUtils;
|
||||
const fileUri = vscode.Uri.file('testUri');
|
||||
@@ -46,6 +49,17 @@ describe('Add SQL Binding quick pick', () => {
|
||||
|
||||
it('Should show error if adding SQL binding was not successful', async function (): Promise<void> {
|
||||
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||
let connectionCreds: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||
let connectionDetails = { options: connectionCreds };
|
||||
// set test vscode-mssql API calls
|
||||
testUtils.vscodeMssqlIExtension.setup(x => x.promptForConnection(true)).returns(() => Promise.resolve(connectionCreds));
|
||||
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString'));
|
||||
testUtils.vscodeMssqlIExtension.setup(x => x.connect(connectionCreds)).returns(() => Promise.resolve('testConnectionURI'));
|
||||
testUtils.vscodeMssqlIExtension.setup(x => x.listDatabases('testConnectionURI')).returns(() => Promise.resolve(['testDb']));
|
||||
const params = { ownerUri: 'testConnectionURI', queryString: azureFunctionUtils.tablesQuery('testDb') };
|
||||
testUtils.vscodeMssqlIExtension.setup(x => x.sendRequest(azureFunctionsContracts.SimpleExecuteRequest.type, params))
|
||||
.returns(() => Promise.resolve({ rowCount: 1, columnInfo: [], rows: [['[schema].[testTable]']] }));
|
||||
|
||||
sinon.stub(azureFunctionService, 'getAzureFunctions').withArgs(fileUri.fsPath).returns(
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
@@ -53,7 +67,7 @@ describe('Add SQL Binding quick pick', () => {
|
||||
azureFunctions: ['af1', 'af2']
|
||||
}));
|
||||
//failure since no AFs are found in the project
|
||||
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(undefined);
|
||||
|
||||
const errormsg = 'Error inserting binding';
|
||||
sinon.stub(azureFunctionService, 'addSqlBinding').withArgs(
|
||||
sinon.match.any, sinon.match.any, sinon.match.any,
|
||||
@@ -65,13 +79,22 @@ describe('Add SQL Binding quick pick', () => {
|
||||
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||
|
||||
// select Azure function
|
||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves({ label: 'af1' });
|
||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').returns(Promise.resolve('af1') as any);
|
||||
// select input or output binding
|
||||
quickpickStub.onSecondCall().resolves(<any>{ label: constants.input, type: BindingType.input });
|
||||
// give object name
|
||||
let inputBoxStub = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dbo.table1');
|
||||
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(vscode.Uri.file('testUri'));
|
||||
// select connection profile - create new
|
||||
quickpickStub.onThirdCall().resolves(<any>{ label: constants.createNewLocalAppSettingWithIcon });
|
||||
// give connection string setting name
|
||||
inputBoxStub.onSecondCall().resolves('sqlConnectionString');
|
||||
sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('sqlConnectionString');
|
||||
quickpickStub.onCall(3).returns(Promise.resolve(constants.connectionProfile) as any);
|
||||
quickpickStub.onCall(4).returns(Promise.resolve(constants.yesString) as any);
|
||||
// setLocalAppSetting fails if we dont set writeFile stub
|
||||
sinon.stub(fs.promises, 'writeFile');
|
||||
sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'sqlConnectionString', 'testConnectionString').returns(Promise.resolve(true));
|
||||
sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package');
|
||||
quickpickStub.onCall(5).returns(Promise.resolve('testDb') as any);
|
||||
quickpickStub.onCall(6).returns(Promise.resolve('[schema].[testTable]') as any);
|
||||
|
||||
await launchAddSqlBindingQuickpick(vscode.Uri.file('testUri'));
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export function createTestCredentials(): vscodeMssql.IConnectionInfo {
|
||||
accountId: 'test-account-id',
|
||||
tenantId: 'test-tenant-id',
|
||||
port: 1234,
|
||||
authenticationType: 'test',
|
||||
authenticationType: 'SqlLogin',
|
||||
azureAccountToken: '',
|
||||
expiresOn: 0,
|
||||
encrypt: false,
|
||||
|
||||
Reference in New Issue
Block a user