mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Load settings from local.settings.json for add sql bindings quickpick (#16957)
* load settings from local.settings.json for add sql bindings quickpick * cleanup * addressing comments
This commit is contained in:
@@ -399,13 +399,14 @@
|
||||
"@types/xml-formatter": "^1.1.0",
|
||||
"@microsoft/ads-extension-telemetry": "^1.1.5",
|
||||
"fast-glob": "^3.1.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"generate-password": "^1.6.0",
|
||||
"jsonc-parser": "^2.3.1",
|
||||
"promisify-child-process": "^3.1.1",
|
||||
"vscode-languageclient": "^5.3.0-next.1",
|
||||
"vscode-nls": "^4.1.2",
|
||||
"xml-formatter": "^2.1.0",
|
||||
"xmldom": "^0.3.0",
|
||||
"generate-password": "^1.6.0",
|
||||
"fs-extra": "^5.0.0"
|
||||
"xmldom": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as utils from './utils';
|
||||
import * as constants from './constants';
|
||||
import { parseJson } from './parseJson';
|
||||
|
||||
/**
|
||||
* Represents the settings in an Azure function project's local.settings.json file
|
||||
*/
|
||||
export interface ILocalSettingsJson {
|
||||
IsEncrypted?: boolean;
|
||||
Values?: { [key: string]: string };
|
||||
Host?: { [key: string]: string };
|
||||
ConnectionStrings?: { [key: string]: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* copied and modified from vscode-azurefunctions extension
|
||||
* @param localSettingsPath full path to local.settings.json
|
||||
* @returns settings in local.settings.json. If no settings are found, returns default "empty" settings
|
||||
*/
|
||||
export async function getLocalSettingsJson(localSettingsPath: string): Promise<ILocalSettingsJson> {
|
||||
if (await fse.pathExists(localSettingsPath)) {
|
||||
const data: string = (await fse.readFile(localSettingsPath)).toString();
|
||||
if (/[^\s]/.test(data)) {
|
||||
try {
|
||||
return parseJson(data);
|
||||
} catch (error) {
|
||||
throw new Error(constants.failedToParse(error.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
IsEncrypted: false // Include this by default otherwise the func cli assumes settings are encrypted and fails to run
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export async function getAFProjectContainingFile(filePath: string): Promise<string | 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)));
|
||||
|
||||
// look for project folder containing file if there's more than one
|
||||
if (functionsProjects.length > 1) {
|
||||
// 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);
|
||||
return undefined;
|
||||
} else if (functionsProjects.length === 0) {
|
||||
throw new Error(constants.noAzureFunctionsProjectsInWorkspace);
|
||||
} else {
|
||||
return functionsProjects[0].fsPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Use 'host.json' as an indicator that this is a functions project
|
||||
// copied from verifyIsproject.ts in vscode-azurefunctions extension
|
||||
export async function isFunctionProject(folderPath: string): Promise<boolean> {
|
||||
return fse.pathExists(path.join(folderPath, constants.hostFileName));
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vscode-nls';
|
||||
import { SqlTargetPlatform } from 'sqldbproj';
|
||||
import * as utils from '../common/utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
export const localize = nls.loadMessageBundle();
|
||||
|
||||
// Placeholder values
|
||||
export const dataSourcesFileName = 'datasources.json';
|
||||
@@ -455,7 +455,10 @@ export const selectAzureFunction = localize('selectAzureFunction', "Select an Az
|
||||
export const sqlObjectToQuery = localize('sqlObjectToQuery', "SQL object to query");
|
||||
export const sqlTableToUpsert = localize('sqlTableToUpsert', "SQL table to upsert into");
|
||||
export const connectionStringSetting = localize('connectionStringSetting', "Connection string setting name");
|
||||
export const selectSetting = localize('selectSetting', "Select SQL connection string setting from local.settings.json");
|
||||
export const connectionStringSettingPlaceholder = localize('connectionStringSettingPlaceholder', "Connection string setting specified in \"local.settings.json\"");
|
||||
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 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); }
|
||||
|
||||
45
extensions/sql-database-projects/src/common/parseJson.ts
Normal file
45
extensions/sql-database-projects/src/common/parseJson.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// copied from vscode-azurefunctions extension
|
||||
|
||||
import * as jsonc from 'jsonc-parser';
|
||||
import * as constants from './constants';
|
||||
|
||||
/**
|
||||
* Parses and returns JSON
|
||||
* Has extra logic to remove a BOM character if it exists and handle comments
|
||||
*/
|
||||
export function parseJson<T extends object>(data: string): T {
|
||||
if (data.charCodeAt(0) === 0xFEFF) {
|
||||
data = data.slice(1);
|
||||
}
|
||||
|
||||
const errors: jsonc.ParseError[] = [];
|
||||
const result: T = <T>jsonc.parse(data, errors, { allowTrailingComma: true });
|
||||
if (errors.length > 0) {
|
||||
const [line, column]: [number, number] = getLineAndColumnFromOffset(data, errors[0].offset);
|
||||
throw new Error(constants.jsonParseError(jsonc.printParseErrorCode(errors[0].error), line, column));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLineAndColumnFromOffset(data: string, offset: number): [number, number] {
|
||||
const lines: string[] = data.split('\n');
|
||||
let charCount: number = 0;
|
||||
let lineCount: number = 0;
|
||||
let column: number = 0;
|
||||
for (const line of lines) {
|
||||
lineCount += 1;
|
||||
const lineLength: number = line.length + 1;
|
||||
charCount += lineLength;
|
||||
if (charCount >= offset) {
|
||||
column = offset - (charCount - lineLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [lineCount, column];
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { BindingType } from 'vscode-mssql';
|
||||
import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
|
||||
import { PackageHelper } from '../tools/packageHelper';
|
||||
|
||||
export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined, packageHelper: PackageHelper): Promise<void> {
|
||||
@@ -73,20 +75,43 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined,
|
||||
}
|
||||
|
||||
// 4. ask for connection string setting name
|
||||
// TODO: load local settings from local.settings.json like in LocalAppSettingListStep in vscode-azurefunctions repo
|
||||
const connectionStringSetting = await vscode.window.showInputBox({
|
||||
prompt: constants.connectionStringSetting,
|
||||
placeHolder: constants.connectionStringSettingPlaceholder,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
let project: string | undefined;
|
||||
try {
|
||||
project = await azureFunctionsUtils.getAFProjectContainingFile(uri.fsPath);
|
||||
} 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
|
||||
}
|
||||
|
||||
if (!connectionStringSetting) {
|
||||
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) : [];
|
||||
|
||||
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
|
||||
connectionStringSettingName = await vscode.window.showInputBox({
|
||||
prompt: constants.connectionStringSetting,
|
||||
placeHolder: constants.connectionStringSettingPlaceholder,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!connectionStringSettingName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. insert binding
|
||||
try {
|
||||
const result = await azureFunctionsService.addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSetting);
|
||||
const result = await azureFunctionsService.addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName);
|
||||
|
||||
if (!result.success) {
|
||||
void vscode.window.showErrorMessage(result.errorMessage);
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as utils from '../common/utils';
|
||||
import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
|
||||
import * as constants from '../common/constants';
|
||||
import { DotNetCommandOptions, NetCoreTool } from './netcoreTool';
|
||||
|
||||
@@ -57,48 +56,19 @@ export class PackageHelper {
|
||||
*/
|
||||
public async addPackageToAFProjectContainingFile(filePath: string, packageName: string, packageVersion?: string): Promise<void> {
|
||||
try {
|
||||
const project = await this.getAFProjectContainingFile(filePath);
|
||||
const project = await azureFunctionsUtils.getAFProjectContainingFile(filePath);
|
||||
|
||||
// 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
|
||||
// user to make sure their project has the package reference
|
||||
if (project) {
|
||||
await this.addPackage(project, packageName, packageVersion);
|
||||
} else {
|
||||
void vscode.window.showInformationMessage(`To use SQL bindings, ensure your Azure Functions project has a reference to ${constants.sqlExtensionPackageName}`);
|
||||
}
|
||||
} catch (e) {
|
||||
void vscode.window.showErrorMessage(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Azure Functions project that contains the given file
|
||||
* @param filePath file that the containing project needs to be found for
|
||||
* @returns filepath of project or undefined if project couldn't be found
|
||||
*/
|
||||
public async getAFProjectContainingFile(filePath: string): Promise<string | 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 => this.isFunctionProject(path.dirname(p.fsPath)));
|
||||
|
||||
// look for project folder containing file if there's more than one
|
||||
if (functionsProjects.length > 1) {
|
||||
// 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
|
||||
void vscode.window.showInformationMessage(`To use SQL bindings, ensure your Azure Functions project has a reference to ${constants.sqlExtensionPackageName}`);
|
||||
console.error('need to find which project contains the file ' + filePath);
|
||||
return undefined;
|
||||
} else if (functionsProjects.length === 0) {
|
||||
throw new Error(constants.noAzureFunctionsProjectsInWorkspace);
|
||||
} else {
|
||||
return functionsProjects[0].fsPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Use 'host.json' as an indicator that this is a functions project
|
||||
// copied from verifyIsproject.ts in vscode-azurefunctions extension
|
||||
async isFunctionProject(folderPath: string): Promise<boolean> {
|
||||
return await fse.pathExists(path.join(folderPath, constants.hostFileName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -799,6 +799,11 @@ json5@^2.1.2:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
jsonc-parser@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
|
||||
integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
|
||||
Reference in New Issue
Block a user