mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Make Create Azure Function with SQL Binding more efficient and simple (#19187)
* initial refactor * fix projectFolder cases * update create azure function with sql binding when no folder is opened * corner case exit browse file * add version since targetFramework is set to specific core tools version * update telemetry and address comments * use project folder instead
This commit is contained in:
@@ -189,21 +189,21 @@ export async function getHostFiles(): Promise<string[] | undefined> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the local.settings.json file path
|
* Gets the local.settings.json file path
|
||||||
* @param projectFile path of the azure function project
|
* @param projectFolder The path to the project the setting should be added to
|
||||||
* @returns the local.settings.json file path
|
* @returns the local.settings.json file path
|
||||||
*/
|
*/
|
||||||
export async function getSettingsFile(projectFile: string): Promise<string | undefined> {
|
export async function getSettingsFile(projectFolder: string): Promise<string | undefined> {
|
||||||
return path.join(path.dirname(projectFile), 'local.settings.json');
|
return path.join(projectFolder, 'local.settings.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the new function file once the file is created and the watcher disposable
|
* New azure function file watcher and watcher disposable to be used to watch for changes to the azure function project
|
||||||
* @param projectFile is the path to the project file
|
* @param projectFolder is the parent directory to the project file
|
||||||
* @returns the function file path once created and the watcher disposable
|
* @returns the function file path once created and the watcher disposable
|
||||||
*/
|
*/
|
||||||
export function waitForNewFunctionFile(projectFile: string): IFileFunctionObject {
|
export function waitForNewFunctionFile(projectFolder: string): IFileFunctionObject {
|
||||||
const watcher = vscode.workspace.createFileSystemWatcher((
|
const watcher = vscode.workspace.createFileSystemWatcher((
|
||||||
path.dirname(projectFile), '**/*.cs'), false, true, true);
|
new vscode.RelativePattern(projectFolder, '**/*.cs')), false, true, true);
|
||||||
const filePromise = new Promise<string>((resolve, _) => {
|
const filePromise = new Promise<string>((resolve, _) => {
|
||||||
watcher.onDidCreate((e) => {
|
watcher.onDidCreate((e) => {
|
||||||
resolve(e.fsPath);
|
resolve(e.fsPath);
|
||||||
@@ -243,12 +243,13 @@ export async function addNugetReferenceToProjectFile(selectedProjectFile: string
|
|||||||
/**
|
/**
|
||||||
* Adds the Sql Connection String to the local.settings.json
|
* Adds the Sql Connection String to the local.settings.json
|
||||||
* @param connectionString of the SQL Server connection that was chosen by the user
|
* @param connectionString of the SQL Server connection that was chosen by the user
|
||||||
* @param projectFile The path to the project the setting should be added to
|
* @param projectFolder The path to the project the setting should be added to
|
||||||
|
* @param settingName The name of the setting to add to the local.settings.json
|
||||||
*/
|
*/
|
||||||
export async function addConnectionStringToConfig(connectionString: string, projectFile: string): Promise<void> {
|
export async function addConnectionStringToConfig(connectionString: string, projectFolder: string, settingName: string = constants.sqlConnectionStringSetting): Promise<void> {
|
||||||
const settingsFile = await getSettingsFile(projectFile);
|
const settingsFile = await getSettingsFile(projectFolder);
|
||||||
if (settingsFile) {
|
if (settingsFile) {
|
||||||
await setLocalAppSetting(path.dirname(settingsFile), constants.sqlConnectionStringSetting, connectionString);
|
await setLocalAppSetting(path.dirname(settingsFile), settingName, connectionString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +427,6 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// show the connection string methods (user input and connection profile options)
|
|
||||||
const listOfConnectionStringMethods = [constants.connectionProfile, constants.userConnectionString];
|
const listOfConnectionStringMethods = [constants.connectionProfile, constants.userConnectionString];
|
||||||
let selectedConnectionStringMethod: string | undefined;
|
let selectedConnectionStringMethod: string | undefined;
|
||||||
let connectionString: string | undefined = '';
|
let connectionString: string | undefined = '';
|
||||||
@@ -560,7 +560,7 @@ export async function promptConnectionStringPasswordAndUpdateConnectionString(co
|
|||||||
// 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
|
// 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) => {
|
void vscode.window.showWarningMessage(constants.userPasswordLater, constants.openFile, constants.closeButton).then(async (result) => {
|
||||||
if (result === constants.openFile) {
|
if (result === constants.openFile) {
|
||||||
// open local.settings.json file
|
// open local.settings.json file (if it exists)
|
||||||
void vscode.commands.executeCommand(constants.vscodeOpenCommand, vscode.Uri.file(localSettingsPath));
|
void vscode.commands.executeCommand(constants.vscodeOpenCommand, vscode.Uri.file(localSettingsPath));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -575,3 +575,21 @@ export async function promptConnectionStringPasswordAndUpdateConnectionString(co
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function promptSelectDatabase(connectionInfo: IConnectionInfo): 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,
|
||||||
|
title: constants.selectDatabase,
|
||||||
|
ignoreFocusOut: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!selectedDatabase) {
|
||||||
|
// User cancelled
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return selectedDatabase;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const timeoutProjectError = localize('timeoutProjectError', 'Timed out wa
|
|||||||
export function errorNewAzureFunction(error: any): string { return localize('errorNewAzureFunction', 'Error creating new Azure Function: {0}', utils.getErrorMessage(error)); }
|
export function errorNewAzureFunction(error: any): string { return localize('errorNewAzureFunction', 'Error creating new Azure Function: {0}', utils.getErrorMessage(error)); }
|
||||||
export const azureFunctionsExtensionNotInstalled = localize('azureFunctionsExtensionNotInstalled', 'Azure Functions extension must be installed in order to use this feature.');
|
export const azureFunctionsExtensionNotInstalled = localize('azureFunctionsExtensionNotInstalled', 'Azure Functions extension must be installed in order to use this feature.');
|
||||||
export const azureFunctionsProjectMustBeOpened = localize('azureFunctionsProjectMustBeOpened', 'A C# Azure Functions project must be present in order to create a new Azure Function for this table.');
|
export const azureFunctionsProjectMustBeOpened = localize('azureFunctionsProjectMustBeOpened', 'A C# Azure Functions project must be present in order to create a new Azure Function for this table.');
|
||||||
|
export const workspaceMustBeUsed = localize('workspaceMustBeUsed', 'The current folder is not a workspace folder. Please open a workspace folder and try again.');
|
||||||
export const needConnection = localize('needConnection', 'A connection is required to use Azure Function with SQL Binding');
|
export const needConnection = localize('needConnection', 'A connection is required to use Azure Function with SQL Binding');
|
||||||
export const selectDatabase = localize('selectDatabase', 'Select Database');
|
export const selectDatabase = localize('selectDatabase', 'Select Database');
|
||||||
export const browseEllipsisWithIcon = `$(folder) ${localize('browseEllipsis', "Browse...")}`;
|
export const browseEllipsisWithIcon = `$(folder) ${localize('browseEllipsis', "Browse...")}`;
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ export enum TelemetryViews {
|
|||||||
export enum TelemetryActions {
|
export enum TelemetryActions {
|
||||||
// Create Azure Function with Sql Binding from Table
|
// Create Azure Function with Sql Binding from Table
|
||||||
startCreateAzureFunctionWithSqlBinding = 'startCreateAzureFunctionWithSqlBinding',
|
startCreateAzureFunctionWithSqlBinding = 'startCreateAzureFunctionWithSqlBinding',
|
||||||
helpCreateAzureFunctionProject = 'helpCreateAzureFunctionProject',
|
|
||||||
learnMore = 'learnMore',
|
|
||||||
finishCreateAzureFunctionWithSqlBinding = 'finishCreateAzureFunctionWithSqlBinding',
|
finishCreateAzureFunctionWithSqlBinding = 'finishCreateAzureFunctionWithSqlBinding',
|
||||||
exitCreateAzureFunctionQuickpick = 'exitCreateAzureFunctionQuickpick',
|
exitCreateAzureFunctionQuickpick = 'exitCreateAzureFunctionQuickpick',
|
||||||
|
|
||||||
@@ -31,3 +29,30 @@ export enum TelemetryActions {
|
|||||||
finishAddSqlBinding = 'finishAddSqlBinding',
|
finishAddSqlBinding = 'finishAddSqlBinding',
|
||||||
exitSqlBindingsQuickpick = 'exitSqlBindingsQuickpick',
|
exitSqlBindingsQuickpick = 'exitSqlBindingsQuickpick',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CreateAzureFunctionStep {
|
||||||
|
getAzureFunctionProject = 'getAzureFunctionProject',
|
||||||
|
learnMore = 'learnMore',
|
||||||
|
helpCreateAzureFunctionProject = 'helpCreateAzureFunctionProject',
|
||||||
|
getSelectedFolder = 'getSelectedFolder',
|
||||||
|
getBindingType = 'getBindingType',
|
||||||
|
launchFromCommandPalette = 'launchFromCommandPalette',
|
||||||
|
launchFromTable = 'launchFromTable',
|
||||||
|
getConnectionProfile = 'getConnectionProfile',
|
||||||
|
getDatabase = 'getDatabase',
|
||||||
|
getObjectName = 'getObjectName',
|
||||||
|
getConnectionString = 'getConnectionString',
|
||||||
|
getAzureFunctionName = 'getAzureFunctionName',
|
||||||
|
getTemplateId = 'getTemplateId',
|
||||||
|
setAzureWebJobsStorage = 'setAzureWebJobsStorage',
|
||||||
|
getConnectionStringSettingName = 'getConnectionStringSettingName',
|
||||||
|
promptForIncludePassword = 'promptForIncludePassword',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExitReason {
|
||||||
|
cancelled = 'cancelled',
|
||||||
|
finishCreate = 'finishCreate',
|
||||||
|
timeout = 'timeout',
|
||||||
|
error = 'error',
|
||||||
|
exit = 'exit'
|
||||||
|
}
|
||||||
@@ -123,11 +123,16 @@ export function timeoutPromise(errorMessage: string, ms: number = 10000): Promis
|
|||||||
* Gets a unique file name
|
* Gets a unique file name
|
||||||
* Increment the file name by adding 1 to function name if the file already exists
|
* Increment the file name by adding 1 to function name if the file already exists
|
||||||
* Undefined if the filename suffix count becomes greater than 1024
|
* Undefined if the filename suffix count becomes greater than 1024
|
||||||
* @param folderPath selected project folder path
|
|
||||||
* @param fileName base filename to use
|
* @param fileName base filename to use
|
||||||
|
* @param folderPath selected project folder path
|
||||||
* @returns a promise with the unique file name, or undefined
|
* @returns a promise with the unique file name, or undefined
|
||||||
*/
|
*/
|
||||||
export async function getUniqueFileName(folderPath: string, fileName: string): Promise<string | undefined> {
|
export async function getUniqueFileName(fileName: string, folderPath?: string): Promise<string | undefined> {
|
||||||
|
if (!folderPath) {
|
||||||
|
// user is creating a brand new azure function project
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let count: number = 0;
|
let count: number = 0;
|
||||||
const maxCount: number = 1024;
|
const maxCount: number = 1024;
|
||||||
let uniqueFileName = fileName;
|
let uniqueFileName = fileName;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as utils from '../common/utils';
|
|||||||
import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
|
import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
|
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
|
||||||
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
|
import { CreateAzureFunctionStep, TelemetryActions, TelemetryReporter, TelemetryViews, ExitReason } from '../common/telemetry';
|
||||||
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
|
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
|
||||||
import { IConnectionInfo, ITreeNodeInfo } from 'vscode-mssql';
|
import { IConnectionInfo, ITreeNodeInfo } from 'vscode-mssql';
|
||||||
|
|
||||||
@@ -20,285 +20,251 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
|||||||
// telemetry properties for create azure function
|
// telemetry properties for create azure function
|
||||||
let sessionId: string = uuid.v4();
|
let sessionId: string = uuid.v4();
|
||||||
let propertyBag: { [key: string]: string } = { sessionId: sessionId };
|
let propertyBag: { [key: string]: string } = { sessionId: sessionId };
|
||||||
let quickPickStep: string = '';
|
let telemetryStep: string = '';
|
||||||
let exitReason: string = 'cancelled';
|
let exitReason: string = ExitReason.cancelled;
|
||||||
TelemetryReporter.sendActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding);
|
TelemetryReporter.sendActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding);
|
||||||
|
|
||||||
let selectedBindingType: BindingType | undefined;
|
|
||||||
let connectionInfo: IConnectionInfo | undefined;
|
let connectionInfo: IConnectionInfo | undefined;
|
||||||
let connectionURI: string;
|
let isCreateNewProject: boolean = false;
|
||||||
let listDatabases: string[] | undefined;
|
let newFunctionFileObject: azureFunctionsUtils.IFileFunctionObject | undefined;
|
||||||
let objectName: string | undefined;
|
|
||||||
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
try {
|
||||||
if (!node) {
|
const azureFunctionApi = await azureFunctionsUtils.getAzureFunctionsExtensionApi();
|
||||||
// if user selects command in command palette we prompt user for information
|
if (!azureFunctionApi) {
|
||||||
quickPickStep = 'launchFromCommandPalette';
|
exitReason = ExitReason.error;
|
||||||
try {
|
propertyBag.exitReason = exitReason;
|
||||||
// Ask binding type for promptObjectName
|
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
||||||
quickPickStep = 'getBindingType';
|
|
||||||
let selectedBinding = await azureFunctionsUtils.promptForBindingType();
|
|
||||||
if (!selectedBinding) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedBindingType = selectedBinding;
|
|
||||||
propertyBag.bindingType = selectedBindingType;
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
.withAdditionalProperties(propertyBag).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Prompts user for azure function project path to use
|
||||||
|
* If multiple found in workspace we prompt user to pick one
|
||||||
|
* If one found in workspace we use that
|
||||||
|
* If none found in workspace we show error message but continue with createFunction message
|
||||||
|
*/
|
||||||
|
let projectFile = await azureFunctionsUtils.getAzureFunctionProject();
|
||||||
|
let projectFolder: string;
|
||||||
|
telemetryStep = CreateAzureFunctionStep.getAzureFunctionProject;
|
||||||
|
if (!projectFile) {
|
||||||
|
while (true) {
|
||||||
|
// show warning message that user needs an azure function project to create a function
|
||||||
|
let projectCreate = await vscode.window.showErrorMessage(constants.azureFunctionsProjectMustBeOpened,
|
||||||
|
constants.createProject, constants.learnMore);
|
||||||
|
if (projectCreate === constants.learnMore) {
|
||||||
|
telemetryStep = CreateAzureFunctionStep.learnMore;
|
||||||
|
exitReason = ExitReason.exit;
|
||||||
|
void vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(constants.sqlBindingsDoc));
|
||||||
|
return;
|
||||||
|
} else if (projectCreate === constants.createProject) {
|
||||||
|
telemetryStep = CreateAzureFunctionStep.helpCreateAzureFunctionProject;
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
|
.withAdditionalProperties(propertyBag).send();
|
||||||
|
|
||||||
|
isCreateNewProject = true;
|
||||||
|
telemetryStep = CreateAzureFunctionStep.getSelectedFolder;
|
||||||
|
// user either has not folder open or an empty workspace
|
||||||
|
// prompt user to choose a folder to create the project in
|
||||||
|
const browseProjectLocation = await vscode.window.showQuickPick(
|
||||||
|
[constants.browseEllipsisWithIcon],
|
||||||
|
{ title: constants.selectAzureFunctionProjFolder, ignoreFocusOut: true });
|
||||||
|
if (!browseProjectLocation) {
|
||||||
|
// User cancelled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const projectFolders = (await vscode.window.showOpenDialog({
|
||||||
|
canSelectFiles: false,
|
||||||
|
canSelectFolders: true,
|
||||||
|
canSelectMany: false,
|
||||||
|
openLabel: constants.selectButton
|
||||||
|
}));
|
||||||
|
if (!projectFolders) {
|
||||||
|
// User cancelled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
projectFolder = projectFolders[0].fsPath;
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
|
.withAdditionalProperties(propertyBag).send();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// user has an azure function project open
|
||||||
|
projectFolder = path.dirname(projectFile);
|
||||||
|
}
|
||||||
|
// create a system file watcher for the project folder
|
||||||
|
newFunctionFileObject = azureFunctionsUtils.waitForNewFunctionFile(projectFolder);
|
||||||
|
|
||||||
|
// Prompt user for binding type
|
||||||
|
telemetryStep = CreateAzureFunctionStep.getBindingType;
|
||||||
|
let selectedBindingType: BindingType | undefined;
|
||||||
|
let selectedBinding = await azureFunctionsUtils.promptForBindingType();
|
||||||
|
if (!selectedBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedBindingType = selectedBinding;
|
||||||
|
propertyBag.bindingType = selectedBindingType;
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
|
.withAdditionalProperties(propertyBag).send();
|
||||||
|
|
||||||
|
|
||||||
|
// Get connection string parameters and construct object name from prompt or connectionInfo given
|
||||||
|
let objectName: string | undefined;
|
||||||
|
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
||||||
|
if (!node) {
|
||||||
|
// if user selects command in command palette we prompt user for information
|
||||||
|
telemetryStep = CreateAzureFunctionStep.launchFromCommandPalette;
|
||||||
|
|
||||||
// prompt user for connection profile to get connection info
|
// prompt user for connection profile to get connection info
|
||||||
quickPickStep = 'getConnectionInfo';
|
telemetryStep = CreateAzureFunctionStep.getConnectionProfile;
|
||||||
connectionInfo = await vscodeMssqlApi.promptForConnection(true);
|
connectionInfo = await vscodeMssqlApi.promptForConnection(true);
|
||||||
if (!connectionInfo) {
|
if (!connectionInfo) {
|
||||||
// User cancelled
|
// User cancelled
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding)
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
.withAdditionalProperties(propertyBag).withConnectionInfo(connectionInfo).send();
|
.withAdditionalProperties(propertyBag).withConnectionInfo(connectionInfo).send();
|
||||||
|
|
||||||
// list databases based on connection profile selected
|
// list databases based on connection profile selected
|
||||||
connectionURI = await vscodeMssqlApi.connect(connectionInfo);
|
telemetryStep = CreateAzureFunctionStep.getDatabase;
|
||||||
listDatabases = await vscodeMssqlApi.listDatabases(connectionURI);
|
let selectedDatabase = await azureFunctionsUtils.promptSelectDatabase(connectionInfo);
|
||||||
const selectedDatabase = (await vscode.window.showQuickPick(listDatabases, {
|
|
||||||
canPickMany: false,
|
|
||||||
title: constants.selectDatabase,
|
|
||||||
ignoreFocusOut: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!selectedDatabase) {
|
if (!selectedDatabase) {
|
||||||
// User cancelled
|
// User cancelled
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
connectionInfo.database = selectedDatabase;
|
connectionInfo.database = selectedDatabase;
|
||||||
|
|
||||||
// prompt user for object name to create function from
|
// prompt user for object name to create function from
|
||||||
|
telemetryStep = CreateAzureFunctionStep.getObjectName;
|
||||||
objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding);
|
objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding);
|
||||||
if (!objectName) {
|
if (!objectName) {
|
||||||
// user cancelled
|
// user cancelled
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
} catch (e) {
|
// if user selects table in tree view we use connection info from Object Explorer node
|
||||||
propertyBag.quickPickStep = quickPickStep;
|
telemetryStep = CreateAzureFunctionStep.launchFromTable;
|
||||||
exitReason = 'error';
|
connectionInfo = node.connectionInfo;
|
||||||
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick, undefined, utils.getErrorType(e))
|
// set the database containing the selected table so it can be used
|
||||||
.withAdditionalProperties(propertyBag).send();
|
// for the initial catalog property of the connection string
|
||||||
return;
|
let newNode: ITreeNodeInfo = node;
|
||||||
}
|
while (newNode) {
|
||||||
} else {
|
if (newNode.nodeType === 'Database') {
|
||||||
quickPickStep = 'launchFromTable';
|
connectionInfo.database = newNode.metadata.name;
|
||||||
connectionInfo = node.connectionInfo;
|
break;
|
||||||
// set the database containing the selected table so it can be used
|
} else {
|
||||||
// for the initial catalog property of the connection string
|
newNode = newNode.parentNode;
|
||||||
let newNode: ITreeNodeInfo = node;
|
}
|
||||||
while (newNode) {
|
|
||||||
if (newNode.nodeType === 'Database') {
|
|
||||||
connectionInfo.database = newNode.metadata.name;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
newNode = newNode.parentNode;
|
|
||||||
}
|
}
|
||||||
|
objectName = utils.generateQuotedFullName(node.metadata.schema, node.metadata.name);
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
|
.withAdditionalProperties(propertyBag).withConnectionInfo(connectionInfo).send();
|
||||||
}
|
}
|
||||||
// Ask binding type for promptObjectName
|
|
||||||
quickPickStep = 'getBindingType';
|
|
||||||
let selectedBinding = await azureFunctionsUtils.promptForBindingType();
|
|
||||||
|
|
||||||
if (!selectedBinding) {
|
// get function name from user
|
||||||
// User cancelled
|
telemetryStep = CreateAzureFunctionStep.getAzureFunctionName;
|
||||||
|
let functionName: string;
|
||||||
|
// remove special characters from function name
|
||||||
|
let uniqueObjectName = utils.santizeObjectName(objectName);
|
||||||
|
let uniqueFunctionName = await utils.getUniqueFileName(uniqueObjectName, projectFolder);
|
||||||
|
functionName = await vscode.window.showInputBox({
|
||||||
|
title: constants.functionNameTitle,
|
||||||
|
value: uniqueFunctionName,
|
||||||
|
ignoreFocusOut: true,
|
||||||
|
validateInput: input => utils.validateFunctionName(input)
|
||||||
|
}) as string;
|
||||||
|
if (!functionName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedBindingType = selectedBinding;
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
propertyBag.bindingType = selectedBinding;
|
.withAdditionalProperties(propertyBag)
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding)
|
.withConnectionInfo(connectionInfo).send();
|
||||||
.withAdditionalProperties(propertyBag).withConnectionInfo(connectionInfo).send();
|
|
||||||
|
|
||||||
objectName = utils.generateQuotedFullName(node.metadata.schema, node.metadata.name);
|
// set the templateId based on the selected binding type
|
||||||
}
|
telemetryStep = CreateAzureFunctionStep.getTemplateId;
|
||||||
|
let templateId: string = selectedBindingType === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID;
|
||||||
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.startCreateAzureFunctionWithSqlBinding)
|
// We need to set the azureWebJobsStorage to a placeholder
|
||||||
.withConnectionInfo(connectionInfo).send();
|
// to suppress the warning for opening the wizard - but will ask them to overwrite if they are creating new azureFunction
|
||||||
quickPickStep = 'getAzureFunctionsExtensionApi';
|
// issue https://github.com/microsoft/azuredatastudio/issues/18780
|
||||||
const azureFunctionApi = await azureFunctionsUtils.getAzureFunctionsExtensionApi();
|
telemetryStep = CreateAzureFunctionStep.setAzureWebJobsStorage;
|
||||||
if (!azureFunctionApi) {
|
await azureFunctionsUtils.setLocalAppSetting(projectFolder, constants.azureWebJobsStorageSetting, constants.azureWebJobsStoragePlaceholder);
|
||||||
propertyBag.exitReason = exitReason;
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
// prompt for Connection String Setting Name
|
||||||
.withConnectionInfo(connectionInfo)
|
let connectionStringSettingName: string | undefined = constants.sqlConnectionStringSetting;
|
||||||
|
if (!isCreateNewProject && projectFile) {
|
||||||
|
telemetryStep = CreateAzureFunctionStep.getConnectionStringSettingName;
|
||||||
|
connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
|
.withAdditionalProperties(propertyBag)
|
||||||
|
.withConnectionInfo(connectionInfo).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create C# Azure Function with SQL Binding
|
||||||
|
telemetryStep = 'createFunctionAPI';
|
||||||
|
await azureFunctionApi.createFunction({
|
||||||
|
language: 'C#',
|
||||||
|
targetFramework: 'netcoreapp3.1',
|
||||||
|
version: '~3',
|
||||||
|
templateId: templateId,
|
||||||
|
functionName: functionName,
|
||||||
|
functionSettings: {
|
||||||
|
connectionStringSetting: connectionStringSettingName,
|
||||||
|
...(selectedBindingType === BindingType.input && { object: objectName }),
|
||||||
|
...(selectedBindingType === BindingType.output && { table: objectName })
|
||||||
|
},
|
||||||
|
folderPath: projectFolder,
|
||||||
|
suppressCreateProjectPrompt: true
|
||||||
|
});
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
|
||||||
|
.withAdditionalProperties(propertyBag)
|
||||||
|
.withConnectionInfo(connectionInfo).send();
|
||||||
|
|
||||||
|
// check for the new function file to be created and dispose of the file system watcher
|
||||||
|
const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError);
|
||||||
|
let functionFilePath = await Promise.race([newFunctionFileObject.filePromise, timeoutForFunctionFile]);
|
||||||
|
|
||||||
|
// prompt user for include password for connection string
|
||||||
|
if (isCreateNewProject && functionFilePath) {
|
||||||
|
telemetryStep = CreateAzureFunctionStep.promptForIncludePassword;
|
||||||
|
let settingsFile = await azureFunctionsUtils.getSettingsFile(projectFolder);
|
||||||
|
if (!settingsFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let connectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, settingsFile);
|
||||||
|
if (!connectionString) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void azureFunctionsUtils.addConnectionStringToConfig(connectionString, projectFolder, connectionStringSettingName);
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyBag.telemetryStep = telemetryStep;
|
||||||
|
exitReason = ExitReason.finishCreate;
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.finishCreateAzureFunctionWithSqlBinding)
|
||||||
|
.withAdditionalProperties(propertyBag)
|
||||||
|
.withConnectionInfo(connectionInfo).send();
|
||||||
|
} catch (error) {
|
||||||
|
let errorType = utils.getErrorType(error);
|
||||||
|
propertyBag.telemetryStep = telemetryStep;
|
||||||
|
if (errorType === 'TimeoutError') {
|
||||||
|
// this error can be cause by many different scenarios including timeout or error occurred during createFunction
|
||||||
|
exitReason = ExitReason.timeout;
|
||||||
|
console.log('Timed out waiting for Azure Function project to be created. This may not necessarily be an error, for example if the user canceled out of the create flow.');
|
||||||
|
} else {
|
||||||
|
// else an error would occur during the createFunction
|
||||||
|
exitReason = ExitReason.error;
|
||||||
|
void vscode.window.showErrorMessage(constants.errorNewAzureFunction(error));
|
||||||
|
}
|
||||||
|
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick, undefined, errorType)
|
||||||
.withAdditionalProperties(propertyBag).send();
|
.withAdditionalProperties(propertyBag).send();
|
||||||
return;
|
return;
|
||||||
|
} finally {
|
||||||
}
|
propertyBag.telemetryStep = telemetryStep;
|
||||||
let projectFile = await azureFunctionsUtils.getAzureFunctionProject();
|
propertyBag.exitReason = exitReason;
|
||||||
let newHostProjectFile!: azureFunctionsUtils.IFileFunctionObject;
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
||||||
let hostFile: string;
|
.withAdditionalProperties(propertyBag).send();
|
||||||
|
if (newFunctionFileObject) {
|
||||||
if (!projectFile) {
|
|
||||||
let projectCreate = await vscode.window.showErrorMessage(constants.azureFunctionsProjectMustBeOpened,
|
|
||||||
constants.createProject, constants.learnMore);
|
|
||||||
if (projectCreate === constants.learnMore) {
|
|
||||||
quickPickStep = 'learnMore';
|
|
||||||
void vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(constants.sqlBindingsDoc));
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.learnMore)
|
|
||||||
.withConnectionInfo(connectionInfo)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
|
||||||
return;
|
|
||||||
} else if (projectCreate === constants.createProject) {
|
|
||||||
quickPickStep = 'helpCreateAzureFunctionProject';
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.helpCreateAzureFunctionProject)
|
|
||||||
.withConnectionInfo(connectionInfo)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
|
||||||
|
|
||||||
// start the create azure function project flow
|
|
||||||
try {
|
|
||||||
// First prompt user for project location. We need to do this ourselves due to an issue
|
|
||||||
// in the AF extension : https://github.com/microsoft/vscode-azurefunctions/issues/3115
|
|
||||||
const browseProjectLocation = await vscode.window.showQuickPick(
|
|
||||||
[constants.browseEllipsisWithIcon],
|
|
||||||
{ title: constants.selectAzureFunctionProjFolder, ignoreFocusOut: true });
|
|
||||||
if (!browseProjectLocation) {
|
|
||||||
// User cancelled
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const projectFolders = (await vscode.window.showOpenDialog({
|
|
||||||
canSelectFiles: false,
|
|
||||||
canSelectFolders: true,
|
|
||||||
canSelectMany: false,
|
|
||||||
openLabel: constants.selectButton
|
|
||||||
}));
|
|
||||||
if (!projectFolders) {
|
|
||||||
// User cancelled
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const templateId: string = selectedBindingType === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID;
|
|
||||||
// because of an AF extension API issue, we have to get the newly created file by adding a watcher
|
|
||||||
// issue: https://github.com/microsoft/vscode-azurefunctions/issues/3052
|
|
||||||
newHostProjectFile = azureFunctionsUtils.waitForNewHostFile();
|
|
||||||
await azureFunctionApi.createFunction({
|
|
||||||
language: 'C#',
|
|
||||||
targetFramework: 'netcoreapp3.1',
|
|
||||||
templateId: templateId,
|
|
||||||
suppressCreateProjectPrompt: true,
|
|
||||||
folderPath: projectFolders[0].fsPath,
|
|
||||||
functionSettings: {
|
|
||||||
...(selectedBindingType === BindingType.input && { object: objectName }),
|
|
||||||
...(selectedBindingType === BindingType.output && { table: objectName })
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const timeoutForHostFile = utils.timeoutPromise(constants.timeoutProjectError);
|
|
||||||
hostFile = await Promise.race([newHostProjectFile.filePromise, timeoutForHostFile]);
|
|
||||||
if (hostFile) {
|
|
||||||
// start the add sql binding flow
|
|
||||||
projectFile = await azureFunctionsUtils.getAzureFunctionProject();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
let errorType = utils.getErrorType(error);
|
|
||||||
propertyBag.quickPickStep = quickPickStep;
|
|
||||||
|
|
||||||
if (errorType === 'TimeoutError') {
|
|
||||||
// this error can be cause by many different scenarios including timeout or error occurred during createFunction
|
|
||||||
exitReason = 'timeout';
|
|
||||||
console.log('Timed out waiting for Azure Function project to be created. This may not necessarily be an error, for example if the user canceled out of the create flow.');
|
|
||||||
} else {
|
|
||||||
// else an error would occur during the createFunction
|
|
||||||
exitReason = 'error';
|
|
||||||
void vscode.window.showErrorMessage(constants.errorNewAzureFunction(error));
|
|
||||||
}
|
|
||||||
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick, undefined, errorType)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
propertyBag.exitReason = exitReason;
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
|
||||||
.withConnectionInfo(connectionInfo)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
|
||||||
newHostProjectFile.watcherDisposable.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectFile) {
|
|
||||||
// because of an AF extension API issue, we have to get the newly created file by adding a watcher
|
|
||||||
// issue: https://github.com/microsoft/vscode-azurefunctions/issues/2908
|
|
||||||
const newFunctionFileObject = azureFunctionsUtils.waitForNewFunctionFile(projectFile);
|
|
||||||
let functionName: string;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// get function name from user
|
|
||||||
quickPickStep = 'getAzureFunctionName';
|
|
||||||
// remove special characters from function name
|
|
||||||
let uniqueObjectName = utils.santizeObjectName(objectName);
|
|
||||||
let uniqueFunctionName = await utils.getUniqueFileName(path.dirname(projectFile), uniqueObjectName);
|
|
||||||
functionName = await vscode.window.showInputBox({
|
|
||||||
title: constants.functionNameTitle,
|
|
||||||
value: uniqueFunctionName,
|
|
||||||
ignoreFocusOut: true,
|
|
||||||
validateInput: input => utils.validateFunctionName(input)
|
|
||||||
}) as string;
|
|
||||||
if (!functionName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.getAzureFunctionProject)
|
|
||||||
.withAdditionalProperties(propertyBag)
|
|
||||||
.withConnectionInfo(connectionInfo).send();
|
|
||||||
|
|
||||||
// set the templateId based on the selected binding type
|
|
||||||
let templateId: string = selectedBindingType === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID;
|
|
||||||
|
|
||||||
// We need to set the azureWebJobsStorage to a placeholder
|
|
||||||
// to suppress the warning for opening the wizard
|
|
||||||
// issue https://github.com/microsoft/azuredatastudio/issues/18780
|
|
||||||
await azureFunctionsUtils.setLocalAppSetting(path.dirname(projectFile), constants.azureWebJobsStorageSetting, constants.azureWebJobsStoragePlaceholder);
|
|
||||||
|
|
||||||
// prompt for connection string setting name and set connection string in local.settings.json
|
|
||||||
quickPickStep = 'getConnectionStringSettingName';
|
|
||||||
let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
|
|
||||||
|
|
||||||
// create C# Azure Function with SQL Binding
|
|
||||||
await azureFunctionApi.createFunction({
|
|
||||||
language: 'C#',
|
|
||||||
templateId: templateId,
|
|
||||||
functionName: functionName,
|
|
||||||
targetFramework: 'netcoreapp3.1',
|
|
||||||
functionSettings: {
|
|
||||||
connectionStringSetting: connectionStringSettingName,
|
|
||||||
...(selectedBindingType === BindingType.input && { object: objectName }),
|
|
||||||
...(selectedBindingType === BindingType.output && { table: objectName })
|
|
||||||
},
|
|
||||||
folderPath: projectFile
|
|
||||||
});
|
|
||||||
|
|
||||||
// check for the new function file to be created and dispose of the file system watcher
|
|
||||||
const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError);
|
|
||||||
await Promise.race([newFunctionFileObject.filePromise, timeoutForFunctionFile]);
|
|
||||||
propertyBag.quickPickStep = quickPickStep;
|
|
||||||
exitReason = 'finishCreate';
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.finishCreateAzureFunctionWithSqlBinding)
|
|
||||||
.withAdditionalProperties(propertyBag)
|
|
||||||
.withConnectionInfo(connectionInfo).send();
|
|
||||||
} catch (e) {
|
|
||||||
let errorType = utils.getErrorType(e);
|
|
||||||
propertyBag.quickPickStep = quickPickStep;
|
|
||||||
|
|
||||||
if (errorType === 'TimeoutError') {
|
|
||||||
// this error can be cause by many different scenarios including timeout or error occurred during createFunction
|
|
||||||
exitReason = 'timeout';
|
|
||||||
console.log('Timed out waiting for Azure Function project to be created. This may not necessarily be an error, for example if the user canceled out of the create flow.');
|
|
||||||
} else {
|
|
||||||
// else an error would occur during the createFunction
|
|
||||||
exitReason = 'error';
|
|
||||||
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
|
|
||||||
}
|
|
||||||
TelemetryReporter.createErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick, undefined, errorType)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
propertyBag.quickPickStep = quickPickStep;
|
|
||||||
propertyBag.exitReason = exitReason;
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
|
||||||
.withConnectionInfo(connectionInfo)
|
|
||||||
.withAdditionalProperties(propertyBag).send();
|
|
||||||
newFunctionFileObject.watcherDisposable.dispose();
|
newFunctionFileObject.watcherDisposable.dispose();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
TelemetryReporter.sendErrorEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.finishCreateAzureFunctionWithSqlBinding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import * as azureFunctionsUtils from '../../common/azureFunctionsUtils';
|
|||||||
|
|
||||||
const rootFolderPath = 'test';
|
const rootFolderPath = 'test';
|
||||||
const localSettingsPath: string = path.join(rootFolderPath, 'local.settings.json');
|
const localSettingsPath: string = path.join(rootFolderPath, 'local.settings.json');
|
||||||
const projectFilePath: string = path.join(rootFolderPath, 'projectFilePath.csproj');
|
|
||||||
|
|
||||||
describe('Tests to verify Azure Functions Utils functions', function (): void {
|
describe('Tests to verify Azure Functions Utils functions', function (): void {
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ describe('Tests to verify Azure Functions Utils functions', function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should get settings file given project file', async () => {
|
it('Should get settings file given project file', async () => {
|
||||||
const settingsFile = await azureFunctionsUtils.getSettingsFile(projectFilePath);
|
const settingsFile = await azureFunctionsUtils.getSettingsFile(rootFolderPath);
|
||||||
should(settingsFile).equals(localSettingsPath);
|
should(settingsFile).equals(localSettingsPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ describe('Tests to verify Azure Functions Utils functions', function (): void {
|
|||||||
const connectionString = 'testConnectionString';
|
const connectionString = 'testConnectionString';
|
||||||
|
|
||||||
let writeFileStub = sinon.stub(fs.promises, 'writeFile');
|
let writeFileStub = sinon.stub(fs.promises, 'writeFile');
|
||||||
await azureFunctionsUtils.addConnectionStringToConfig(connectionString, projectFilePath);
|
await azureFunctionsUtils.addConnectionStringToConfig(connectionString, rootFolderPath);
|
||||||
should(writeFileStub.calledWithExactly(localSettingsPath, `{\n "IsEncrypted": false,\n "Values": {\n "test1": "test1",\n "test2": "test2",\n "test3": "test3",\n "SqlConnectionString": "testConnectionString"\n }\n}`)).equals(true);
|
should(writeFileStub.calledWithExactly(localSettingsPath, `{\n "IsEncrypted": false,\n "Values": {\n "test1": "test1",\n "test2": "test2",\n "test3": "test3",\n "SqlConnectionString": "testConnectionString"\n }\n}`)).equals(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ describe('Add SQL Binding quick pick', () => {
|
|||||||
// give object name
|
// give object name
|
||||||
let inputBoxStub = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dbo.table1');
|
let inputBoxStub = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dbo.table1');
|
||||||
|
|
||||||
// select connection profile
|
// select connection string setting name
|
||||||
quickpickStub.onThirdCall().resolves({ label: constants.createNewLocalAppSettingWithIcon });
|
quickpickStub.onThirdCall().resolves({ label: constants.createNewLocalAppSettingWithIcon });
|
||||||
|
|
||||||
// give connection string setting name
|
// give connection string setting name
|
||||||
|
|||||||
Reference in New Issue
Block a user