diff --git a/extensions/sql-database-projects/src/common/azureFunctionsUtils.ts b/extensions/sql-database-projects/src/common/azureFunctionsUtils.ts index e18591bd26..9659b8747f 100644 --- a/extensions/sql-database-projects/src/common/azureFunctionsUtils.ts +++ b/extensions/sql-database-projects/src/common/azureFunctionsUtils.ts @@ -41,12 +41,42 @@ export async function getLocalSettingsJson(localSettingsPath: string): Promise { + const localSettingsPath: string = path.join(projectFolder, constants.azureFunctionLocalSettingsFileName); + const settings: ILocalSettingsJson = await getLocalSettingsJson(localSettingsPath); + + settings.Values = settings.Values || {}; + if (settings.Values[key] === value) { + // don't do anything if it's the same as the existing value + return true; + } else if (settings.Values[key]) { + const result = await vscode.window.showWarningMessage(constants.settingAlreadyExists(key), { modal: true }, constants.yesString); + if (result !== constants.yesString) { + // key already exists and user doesn't want to overwrite it + return false; + } + } + + settings.Values[key] = value; + await fse.writeJson(localSettingsPath, settings, { spaces: 2 }); + + return true; +} + /** * Gets the Azure Functions project that contains the given file if the project is open in one of the workspace folders * @param filePath file that the containing project needs to be found for - * @returns filepath of project or undefined if project couldn't be found + * @returns uri of project or undefined if project couldn't be found */ -export async function getAFProjectContainingFile(filePath: string): Promise { +export async function getAFProjectContainingFile(fileUri: vscode.Uri): Promise { // get functions csprojs in the workspace const projectPromises = vscode.workspace.workspaceFolders?.map(f => utils.getAllProjectsInFolder(f.uri, '.csproj')) ?? []; const functionsProjects = (await Promise.all(projectPromises)).reduce((prev, curr) => prev.concat(curr), []).filter(p => isFunctionProject(path.dirname(p.fsPath))); @@ -56,12 +86,12 @@ export async function getAFProjectContainingFile(filePath: string): Promise { + return { + label: setting + } as vscode.QuickPickItem & { isCreateNew?: boolean }; + }); + } + + existingSettings.unshift({ label: constants.createNewLocalAppSettingWithIcon, isCreateNew: true }); + + while (!connectionStringSettingName) { + const selectedSetting = await vscode.window.showQuickPick(existingSettings, { + canPickMany: false, + title: constants.selectSetting, + ignoreFocusOut: true + }); + if (!selectedSetting) { + // User cancelled + return; + } + + if (selectedSetting.isCreateNew) { + const newConnectionStringSettingName = await vscode.window.showInputBox( + { + title: constants.enterConnectionStringSettingName, + ignoreFocusOut: true, + 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; + } + + const newConnectionStringValue = await vscode.window.showInputBox( + { + title: constants.enterConnectionString, + ignoreFocusOut: true, + validateInput: input => input ? undefined : constants.valueMustNotBeEmpty + } + ) ?? ''; + + if (!newConnectionStringValue) { + // go back to select setting quickpick if user escapes from inputting the value in case they changed their mind + continue; + } + + try { + const success = await azureFunctionsUtils.setLocalAppSetting(path.dirname(projectUri.fsPath), newConnectionStringSettingName, newConnectionStringValue); + if (success) { + connectionStringSettingName = newConnectionStringSettingName; + } + } catch (e) { + // display error message and show select setting quickpick again + void vscode.window.showErrorMessage(e); + } + // 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 + } else { + connectionStringSettingName = selectedSetting.label; + } + } - connectionStringSettingName = await vscode.window.showQuickPick(existingSettings, { - canPickMany: false, - title: constants.selectSetting, - ignoreFocusOut: true - }); } 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 @@ -132,6 +196,6 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined, } // 6. Add sql extension package reference to project. If the reference is already there, it doesn't get added again - await packageHelper.addPackageToAFProjectContainingFile(uri.fsPath, constants.sqlExtensionPackageName); + await packageHelper.addPackageToAFProjectContainingFile(uri, constants.sqlExtensionPackageName); } diff --git a/extensions/sql-database-projects/src/test/packageHelper.test.ts b/extensions/sql-database-projects/src/test/packageHelper.test.ts index 840e27c742..5ef978d2a0 100644 --- a/extensions/sql-database-projects/src/test/packageHelper.test.ts +++ b/extensions/sql-database-projects/src/test/packageHelper.test.ts @@ -27,27 +27,27 @@ describe('PackageHelper tests', function (): void { it('Should construct correct add Package Arguments', function (): void { const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test')); - const projectPath = 'dummy\\project\\path.csproj'; - const result = packageHelper.constructAddPackageArguments(projectPath, constants.sqlExtensionPackageName); + const projectUri = vscode.Uri.file('dummy\\project\\path.csproj'); + const result = packageHelper.constructAddPackageArguments(projectUri, constants.sqlExtensionPackageName); if (os.platform() === 'win32') { - should(result).equal(` add "dummy\\\\project\\\\path.csproj" package ${constants.sqlExtensionPackageName} --prerelease`); + should(result).equal(` add "\\\\dummy\\\\project\\\\path.csproj" package ${constants.sqlExtensionPackageName} --prerelease`); } else { - should(result).equal(` add "dummy/project/path.csproj" package ${constants.sqlExtensionPackageName} --prerelease`); + should(result).equal(` add "/dummy/project/path.csproj" package ${constants.sqlExtensionPackageName} --prerelease`); } }); it('Should construct correct add Package Arguments with version', function (): void { const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test')); - const projectPath = 'dummy\\project\\path.csproj'; - const result = packageHelper.constructAddPackageArguments(projectPath, constants.sqlExtensionPackageName, constants.VersionNumber); + const projectUri = vscode.Uri.file('dummy\\project\\path.csproj'); + const result = packageHelper.constructAddPackageArguments(projectUri, constants.sqlExtensionPackageName, constants.VersionNumber); if (os.platform() === 'win32') { - should(result).equal(` add "dummy\\\\project\\\\path.csproj" package ${constants.sqlExtensionPackageName} -v ${constants.VersionNumber}`); + should(result).equal(` add "\\\\dummy\\\\project\\\\path.csproj" package ${constants.sqlExtensionPackageName} -v ${constants.VersionNumber}`); } else { - should(result).equal(` add "dummy/project/path.csproj" package ${constants.sqlExtensionPackageName} -v ${constants.VersionNumber}`); + should(result).equal(` add "/dummy/project/path.csproj" package ${constants.sqlExtensionPackageName} -v ${constants.VersionNumber}`); } }); @@ -55,7 +55,7 @@ describe('PackageHelper tests', function (): void { sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(undefined); const spy = sinon.spy(vscode.window, 'showInformationMessage'); - await packageHelper.addPackageToAFProjectContainingFile('', constants.sqlExtensionPackageName); + await packageHelper.addPackageToAFProjectContainingFile(vscode.Uri.file(''), constants.sqlExtensionPackageName); should(spy.calledOnce).be.true('showInformationMessage should have been called exactly once'); }); }); diff --git a/extensions/sql-database-projects/src/tools/packageHelper.ts b/extensions/sql-database-projects/src/tools/packageHelper.ts index b983b63fbe..02af2b68e2 100644 --- a/extensions/sql-database-projects/src/tools/packageHelper.ts +++ b/extensions/sql-database-projects/src/tools/packageHelper.ts @@ -18,13 +18,13 @@ export class PackageHelper { /** * Constructs the parameters for a dotnet add package - * @param projectPath full path to project to add package to + * @param projectUri uri of project to add package to * @param packageName name of package * @param packageVersion optional version of package. If none, latest will be pulled in * @returns string constructed with the arguments for dotnet add package */ - public constructAddPackageArguments(projectPath: string, packageName: string, packageVersion?: string): string { - projectPath = utils.getQuotedPath(projectPath); + public constructAddPackageArguments(projectUri: vscode.Uri, packageName: string, packageVersion?: string): string { + const projectPath = utils.getQuotedPath(projectUri.fsPath); if (packageVersion) { return ` add ${projectPath} package ${packageName} -v ${packageVersion}`; } else { @@ -36,14 +36,14 @@ export class PackageHelper { /** * Runs dotnet add package to add a package reference to the specified project. If the project already has a package reference * for this package version, the project file won't get updated - * @param projectPath full path to project to add package to + * @param projectPath uri of project to add package to * @param packageName name of package * @param packageVersion optional version of package. If none, latest will be pulled in */ - public async addPackage(project: string, packageName: string, packageVersion?: string): Promise { + public async addPackage(projectUri: vscode.Uri, packageName: string, packageVersion?: string): Promise { const addOptions: ShellCommandOptions = { commandTitle: constants.addPackage, - argument: this.constructAddPackageArguments(project, packageName, packageVersion) + argument: this.constructAddPackageArguments(projectUri, packageName, packageVersion) }; await this.netCoreTool.runDotnetCommand(addOptions); @@ -51,13 +51,13 @@ export class PackageHelper { /** * Adds specified package to Azure Functions project the specified file is a part of - * @param filePath full path to file to find the containing AF project of to add package reference to + * @param filePath uri of file to find the containing AF project of to add package reference to * @param packageName package to add reference to * @param packageVersion optional version of package. If none, latest will be pulled in */ - public async addPackageToAFProjectContainingFile(filePath: string, packageName: string, packageVersion?: string): Promise { + public async addPackageToAFProjectContainingFile(fileUri: vscode.Uri, packageName: string, packageVersion?: string): Promise { try { - const project = await azureFunctionsUtils.getAFProjectContainingFile(filePath); + const project = await azureFunctionsUtils.getAFProjectContainingFile(fileUri); // if no AF projects were found, an error gets thrown from getAFProjectContainingFile(). This check is temporary until // multiple AF projects in the workspace is handled. That scenario returns undefined and shows an info message telling the