mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -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
|
* 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
|
* @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
|
// get functions csprojs in the workspace
|
||||||
const projectPromises = vscode.workspace.workspaceFolders?.map(f => utils.getAllProjectsInFolder(f.uri, '.csproj')) ?? [];
|
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)));
|
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
|
// 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
|
// 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
|
// 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;
|
return undefined;
|
||||||
} else if (functionsProjects.length === 0) {
|
} else if (functionsProjects.length === 0) {
|
||||||
throw new Error(constants.noAzureFunctionsProjectsInWorkspace);
|
throw new Error(constants.noAzureFunctionsProjectsInWorkspace);
|
||||||
} else {
|
} 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 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 noAzureFunctionsProjectsInWorkspace = localize('noAzureFunctionsProjectsInWorkspace', "No Azure functions projects found in the workspace");
|
||||||
export const addPackage = localize('addPackage', "Add Package");
|
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 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); }
|
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
|
// 4. ask for connection string setting name
|
||||||
let project: string | undefined;
|
let projectUri: vscode.Uri | undefined;
|
||||||
try {
|
try {
|
||||||
project = await azureFunctionsUtils.getAFProjectContainingFile(uri.fsPath);
|
projectUri = await azureFunctionsUtils.getAFProjectContainingFile(uri);
|
||||||
} catch (e) {
|
} 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
|
// 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;
|
let connectionStringSettingName;
|
||||||
|
|
||||||
// show the settings from project's local.settings.json if there's an AF functions project
|
// 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 (projectUri) {
|
||||||
if (project) {
|
let settings;
|
||||||
const settings = await azureFunctionsUtils.getLocalSettingsJson(path.join(path.dirname(project!), constants.azureFunctionLocalSettingsFileName));
|
try {
|
||||||
const existingSettings: string[] = settings.Values ? Object.keys(settings.Values) : [];
|
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 {
|
} else {
|
||||||
// if no AF project was found or there's more than one AF functions project in the workspace,
|
// 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
|
// 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
|
// 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 {
|
it('Should construct correct add Package Arguments', function (): void {
|
||||||
const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test'));
|
const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test'));
|
||||||
const projectPath = 'dummy\\project\\path.csproj';
|
const projectUri = vscode.Uri.file('dummy\\project\\path.csproj');
|
||||||
const result = packageHelper.constructAddPackageArguments(projectPath, constants.sqlExtensionPackageName);
|
const result = packageHelper.constructAddPackageArguments(projectUri, constants.sqlExtensionPackageName);
|
||||||
|
|
||||||
if (os.platform() === 'win32') {
|
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 {
|
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 {
|
it('Should construct correct add Package Arguments with version', function (): void {
|
||||||
const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test'));
|
const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test'));
|
||||||
const projectPath = 'dummy\\project\\path.csproj';
|
const projectUri = vscode.Uri.file('dummy\\project\\path.csproj');
|
||||||
const result = packageHelper.constructAddPackageArguments(projectPath, constants.sqlExtensionPackageName, constants.VersionNumber);
|
const result = packageHelper.constructAddPackageArguments(projectUri, constants.sqlExtensionPackageName, constants.VersionNumber);
|
||||||
|
|
||||||
if (os.platform() === 'win32') {
|
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 {
|
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);
|
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(undefined);
|
||||||
const spy = sinon.spy(vscode.window, 'showInformationMessage');
|
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');
|
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
|
* 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 packageName name of package
|
||||||
* @param packageVersion optional version of package. If none, latest will be pulled in
|
* @param packageVersion optional version of package. If none, latest will be pulled in
|
||||||
* @returns string constructed with the arguments for dotnet add package
|
* @returns string constructed with the arguments for dotnet add package
|
||||||
*/
|
*/
|
||||||
public constructAddPackageArguments(projectPath: string, packageName: string, packageVersion?: string): string {
|
public constructAddPackageArguments(projectUri: vscode.Uri, packageName: string, packageVersion?: string): string {
|
||||||
projectPath = utils.getQuotedPath(projectPath);
|
const projectPath = utils.getQuotedPath(projectUri.fsPath);
|
||||||
if (packageVersion) {
|
if (packageVersion) {
|
||||||
return ` add ${projectPath} package ${packageName} -v ${packageVersion}`;
|
return ` add ${projectPath} package ${packageName} -v ${packageVersion}`;
|
||||||
} else {
|
} 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
|
* 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
|
* 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 packageName name of package
|
||||||
* @param packageVersion optional version of package. If none, latest will be pulled in
|
* @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 = {
|
const addOptions: ShellCommandOptions = {
|
||||||
commandTitle: constants.addPackage,
|
commandTitle: constants.addPackage,
|
||||||
argument: this.constructAddPackageArguments(project, packageName, packageVersion)
|
argument: this.constructAddPackageArguments(projectUri, packageName, packageVersion)
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.netCoreTool.runDotnetCommand(addOptions);
|
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
|
* 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 packageName package to add reference to
|
||||||
* @param packageVersion optional version of package. If none, latest will be pulled in
|
* @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 {
|
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
|
// 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
|
// 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