mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 01:25:36 -05:00
Add support for adding new setting in local.settings.json in add SQL binding quickpick (#17093)
* be able to add new setting in local.settings.json * cleanup * addressing comments * remove todo comment * addressing comments * update some strings to uris
This commit is contained in:
@@ -41,12 +41,42 @@ export async function getLocalSettingsJson(localSettingsPath: string): Promise<I
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new setting to a project's local.settings.json file
|
||||
* modified from setLocalAppSetting code from vscode-azurefunctions extension
|
||||
* @param projectFolder full path to project folder
|
||||
* @param key Key of the new setting
|
||||
* @param value Value of the new setting
|
||||
* @returns true if successful adding the new setting, false if unsuccessful
|
||||
*/
|
||||
export async function setLocalAppSetting(projectFolder: string, key: string, value: string): Promise<boolean> {
|
||||
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<string | undefined> {
|
||||
export async function getAFProjectContainingFile(fileUri: vscode.Uri): Promise<vscode.Uri | undefined> {
|
||||
// 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<stri
|
||||
// TODO: figure out which project contains the file
|
||||
// the new style csproj doesn't list all the files in the project anymore, unless the file isn't in the same folder
|
||||
// so we can't rely on using that to check
|
||||
console.error('need to find which project contains the file ' + filePath);
|
||||
console.error('need to find which project contains the file ' + fileUri.fsPath);
|
||||
return undefined;
|
||||
} else if (functionsProjects.length === 0) {
|
||||
throw new Error(constants.noAzureFunctionsProjectsInWorkspace);
|
||||
} else {
|
||||
return functionsProjects[0].fsPath;
|
||||
return functionsProjects[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -479,5 +479,11 @@ export const connectionStringSettingPlaceholder = localize('connectionStringSett
|
||||
export const noAzureFunctionsInFile = localize('noAzureFunctionsInFile', "No Azure functions in the current active file");
|
||||
export const noAzureFunctionsProjectsInWorkspace = localize('noAzureFunctionsProjectsInWorkspace', "No Azure functions projects found in the workspace");
|
||||
export const addPackage = localize('addPackage', "Add Package");
|
||||
export const createNewLocalAppSetting = localize('createNewLocalAppSetting', 'Create new local app setting');
|
||||
export const createNewLocalAppSettingWithIcon = `$(add) ${createNewLocalAppSetting}`;
|
||||
export const valueMustNotBeEmpty = localize('valueMustNotBeEmpty', "Value must not be empty");
|
||||
export const enterConnectionStringSettingName = localize('enterConnectionStringSettingName', "Enter connection string setting name");
|
||||
export const enterConnectionString = localize('enterConnectionString', "Enter connection string");
|
||||
export function settingAlreadyExists(settingName: string) { return localize('SettingAlreadyExists', 'Local app setting \'{0}\' already exists. Overwrite?', settingName); }
|
||||
export function failedToParse(errorMessage: string) { return localize('failedToParse', 'Failed to parse "{0}": {1}.', azureFunctionLocalSettingsFileName, errorMessage); }
|
||||
export function jsonParseError(error: string, line: number, column: number) { return localize('jsonParseError', '{0} near line "{1}", column "{2}"', error, line, column); }
|
||||
|
||||
@@ -78,9 +78,9 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined,
|
||||
}
|
||||
|
||||
// 4. ask for connection string setting name
|
||||
let project: string | undefined;
|
||||
let projectUri: vscode.Uri | undefined;
|
||||
try {
|
||||
project = await azureFunctionsUtils.getAFProjectContainingFile(uri.fsPath);
|
||||
projectUri = await azureFunctionsUtils.getAFProjectContainingFile(uri);
|
||||
} catch (e) {
|
||||
// continue even if there's no AF project found. The binding should still be able to be added as long as there was an azure function found in the file earlier
|
||||
}
|
||||
@@ -88,16 +88,80 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined,
|
||||
let connectionStringSettingName;
|
||||
|
||||
// show the settings from project's local.settings.json if there's an AF functions project
|
||||
// TODO: allow new setting name to get added here and added to local.settings.json
|
||||
if (project) {
|
||||
const settings = await azureFunctionsUtils.getLocalSettingsJson(path.join(path.dirname(project!), constants.azureFunctionLocalSettingsFileName));
|
||||
const existingSettings: string[] = settings.Values ? Object.keys(settings.Values) : [];
|
||||
if (projectUri) {
|
||||
let settings;
|
||||
try {
|
||||
settings = await azureFunctionsUtils.getLocalSettingsJson(path.join(path.dirname(projectUri.fsPath!), constants.azureFunctionLocalSettingsFileName));
|
||||
} catch (e) {
|
||||
void vscode.window.showErrorMessage(e);
|
||||
return;
|
||||
}
|
||||
|
||||
let existingSettings: (vscode.QuickPickItem & { isCreateNew?: boolean })[] = [];
|
||||
if (settings?.Values) {
|
||||
existingSettings = Object.keys(settings.Values).map(setting => {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<void> {
|
||||
public async addPackage(projectUri: vscode.Uri, packageName: string, packageVersion?: string): Promise<void> {
|
||||
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<void> {
|
||||
public async addPackageToAFProjectContainingFile(fileUri: vscode.Uri, packageName: string, packageVersion?: string): Promise<void> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user