mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 09:35:37 -05:00
Remove sql-bindings logic from sql-database-projects (#18754)
* remove sql-bindings logic from sql-database-projects
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 fileUri file that the containing project needs to be found for
|
||||
* @returns uri of project or undefined if project couldn't be found
|
||||
*/
|
||||
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)));
|
||||
|
||||
// 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 ' + fileUri.fsPath);
|
||||
return undefined;
|
||||
} else if (functionsProjects.length === 0) {
|
||||
throw new Error(constants.noAzureFunctionsProjectsInWorkspace);
|
||||
} else {
|
||||
return functionsProjects[0];
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
@@ -533,48 +533,3 @@ export const defaultDSP = targetPlatformToVersion.get(defaultTargetPlatform)!;
|
||||
export function getTargetPlatformFromVersion(version: string): string {
|
||||
return Array.from(targetPlatformToVersion.keys()).filter(k => targetPlatformToVersion.get(k) === version)[0];
|
||||
}
|
||||
|
||||
// Insert SQL binding
|
||||
export const hostFileName = 'host.json';
|
||||
export const sqlExtensionPackageName = 'Microsoft.Azure.WebJobs.Extensions.Sql';
|
||||
export const placeHolderObject = '[dbo].[table1]';
|
||||
export const sqlBindingsHelpLink = 'https://github.com/Azure/azure-functions-sql-extension/blob/main/README.md';
|
||||
export const passwordPlaceholder = '******';
|
||||
|
||||
export const input = localize('input', "Input");
|
||||
export const output = localize('output', "Output");
|
||||
export const selectBindingType = localize('selectBindingType', "Select type of binding");
|
||||
export const selectAzureFunction = localize('selectAzureFunction', "Select an Azure function in the current file to add SQL binding to");
|
||||
export const sqlTableOrViewToQuery = localize('sqlTableOrViewToQuery', "SQL table or view 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 const createNewLocalAppSetting = localize('createNewLocalAppSetting', 'Create new local app setting');
|
||||
export const createNewLocalAppSettingWithIcon = `$(add) ${createNewLocalAppSetting}`;
|
||||
export const sqlConnectionStringSetting = 'SqlConnectionString';
|
||||
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 const saveChangesInFile = localize('saveChangesInFile', "There are unsaved changes in the current file. Save now?");
|
||||
export const save = localize('save', "Save");
|
||||
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); }
|
||||
export const moreInformation = localize('moreInformation', "More Information");
|
||||
export const addPackageReferenceMessage = localize('addPackageReferenceMessage', 'To use SQL bindings, ensure your Azure Functions project has a reference to {0}', sqlExtensionPackageName);
|
||||
export const addSqlBindingPackageError = localize('addSqlBindingPackageError', 'Error adding Sql Binding extension package to project');
|
||||
export const failedToGetConnectionString = localize('failedToGetConnectionString', 'An error occurred generating the connection string for the selected connection');
|
||||
export const connectionProfile = localize('connectionProfile', 'Select a connection profile');
|
||||
export const userConnectionString = localize('userConnectionString', 'Enter connection string');
|
||||
export const selectConnectionString = localize('selectConnectionString', 'Select SQL connection string method');
|
||||
export const selectConnectionError = (err?: any) => err ? localize('selectConnectionError', "Failed to set connection string app setting: {0}", utils.getErrorMessage(err)) : localize('unableToSetConnectionString', "Failed to set connection string app setting");
|
||||
export const includePassword = localize('includePassword', 'Do you want to include the password from this connection in your local.settings.json file?');
|
||||
export const enterPasswordPrompt = localize('enterPasswordPrompt', 'Enter the password to be used for the connection string');
|
||||
export const enterPasswordManually = localize('enterPasswordManually', 'Enter password or press escape to cancel');
|
||||
export const userPasswordLater = localize('userPasswordLater', 'In order to user the SQL connection string later you will need to manually enter the password in your local.settings.json file.');
|
||||
export const openFile = localize('openFile', "Open File");
|
||||
export const closeButton = localize('closeButton', "Close");
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt 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];
|
||||
}
|
||||
@@ -16,7 +16,6 @@ export enum TelemetryViews {
|
||||
ProjectController = 'ProjectController',
|
||||
SqlProjectPublishDialog = 'SqlProjectPublishDialog',
|
||||
ProjectTree = 'ProjectTree',
|
||||
SqlBindingsQuickPick = 'SqlBindingsQuickPick'
|
||||
}
|
||||
|
||||
export enum TelemetryActions {
|
||||
@@ -33,7 +32,5 @@ export enum TelemetryActions {
|
||||
build = 'build',
|
||||
updateProjectForRoundtrip = 'updateProjectForRoundtrip',
|
||||
changePlatformType = 'changePlatformType',
|
||||
updateSystemDatabaseReferencesInProjFile = 'updateSystemDatabaseReferencesInProjFile',
|
||||
startAddSqlBinding = 'startAddSqlBinding',
|
||||
finishAddSqlBinding = 'finishAddSqlBinding'
|
||||
updateSystemDatabaseReferencesInProjFile = 'updateSystemDatabaseReferencesInProjFile'
|
||||
}
|
||||
|
||||
@@ -302,16 +302,6 @@ export async function getSchemaCompareService(): Promise<ISchemaCompareService>
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAzureFunctionService(): Promise<vscodeMssql.IAzureFunctionsService> {
|
||||
if (getAzdataApi()) {
|
||||
// this isn't supported in ADS
|
||||
throw new Error('Azure Functions service is not supported in Azure Data Studio');
|
||||
} else {
|
||||
const api = await getVscodeMssqlApi();
|
||||
return api.azureFunctions;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVscodeMssqlApi(): Promise<vscodeMssql.IExtension> {
|
||||
const ext = vscode.extensions.getExtension(vscodeMssql.extension.name) as vscode.Extension<vscodeMssql.IExtension>;
|
||||
return ext.activate();
|
||||
@@ -475,23 +465,6 @@ export async function detectCommandInstallation(command: string): Promise<boolea
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the projects of the specified extension in the folder
|
||||
* @param folder
|
||||
* @param projectExtension project extension to filter on
|
||||
* @returns array of project uris
|
||||
*/
|
||||
export async function getAllProjectsInFolder(folder: vscode.Uri, projectExtension: string): Promise<vscode.Uri[]> {
|
||||
// path needs to use forward slashes for glob to work
|
||||
const escapedPath = glob.escapePath(folder.fsPath.replace(/\\/g, '/'));
|
||||
|
||||
// filter for projects with the specified project extension
|
||||
const projFilter = path.posix.join(escapedPath, '**', `*${projectExtension}`);
|
||||
|
||||
// glob will return an array of file paths with forward slashes, so they need to be converted back if on windows
|
||||
return (await glob(projFilter)).map(p => vscode.Uri.file(path.resolve(p)));
|
||||
}
|
||||
|
||||
export function validateSqlServerPortNumber(port: string | undefined): boolean {
|
||||
if (!port) {
|
||||
return false;
|
||||
|
||||
@@ -16,8 +16,6 @@ import { IconPathHelper } from '../common/iconHelper';
|
||||
import { WorkspaceTreeItem } from 'dataworkspace';
|
||||
import * as constants from '../common/constants';
|
||||
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
|
||||
import { launchAddSqlBindingQuickpick } from '../dialogs/addSqlBindingQuickpick';
|
||||
import { PackageHelper } from '../tools/packageHelper';
|
||||
import { GenerateProjectFromOpenApiSpecOptions } from 'sqldbproj';
|
||||
|
||||
/**
|
||||
@@ -26,13 +24,11 @@ import { GenerateProjectFromOpenApiSpecOptions } from 'sqldbproj';
|
||||
export default class MainController implements vscode.Disposable {
|
||||
protected projectsController: ProjectsController;
|
||||
protected netcoreTool: NetCoreTool;
|
||||
protected packageHelper: PackageHelper;
|
||||
private _outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(constants.projectsOutputChannel);
|
||||
|
||||
public constructor(private context: vscode.ExtensionContext) {
|
||||
this.projectsController = new ProjectsController(this._outputChannel);
|
||||
this.netcoreTool = new NetCoreTool(this._outputChannel);
|
||||
this.packageHelper = new PackageHelper(this._outputChannel);
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
@@ -89,8 +85,6 @@ export default class MainController implements vscode.Disposable {
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.changeTargetPlatform', async (node: WorkspaceTreeItem) => { return this.projectsController.changeTargetPlatform(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.validateExternalStreamingJob', async (node: WorkspaceTreeItem) => { return this.projectsController.validateExternalStreamingJob(node); });
|
||||
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.addSqlBinding', async (uri: vscode.Uri | undefined) => { return launchAddSqlBindingQuickpick(uri, this.packageHelper); });
|
||||
|
||||
IconPathHelper.setExtensionContext(this.extensionContext);
|
||||
|
||||
await templates.loadTemplates(path.join(this.context.extensionPath, 'resources', 'templates'));
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { BindingType, ConnectionDetails, IConnectionInfo } 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';
|
||||
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
|
||||
|
||||
export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined, packageHelper: PackageHelper): Promise<void> {
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.startAddSqlBinding);
|
||||
|
||||
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
|
||||
|
||||
if (!uri) {
|
||||
// this command only shows in the command palette when the active editor is a .cs file, so we can safely assume that's the scenario
|
||||
// when this is called without a uri
|
||||
uri = vscode.window.activeTextEditor!.document.uri;
|
||||
|
||||
if (vscode.window.activeTextEditor!.document.isDirty) {
|
||||
const result = await vscode.window.showWarningMessage(constants.saveChangesInFile, { modal: true }, constants.save);
|
||||
|
||||
if (result !== constants.save) {
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.window.activeTextEditor!.document.save();
|
||||
}
|
||||
}
|
||||
|
||||
// get all the Azure functions in the file
|
||||
const azureFunctionsService = await utils.getAzureFunctionService();
|
||||
let getAzureFunctionsResult;
|
||||
try {
|
||||
getAzureFunctionsResult = await azureFunctionsService.getAzureFunctions(uri.fsPath);
|
||||
} catch (e) {
|
||||
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
const azureFunctions = getAzureFunctionsResult.azureFunctions;
|
||||
|
||||
if (azureFunctions.length === 0) {
|
||||
void vscode.window.showErrorMessage(constants.noAzureFunctionsInFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. select Azure function from the current file
|
||||
const azureFunctionName = (await vscode.window.showQuickPick(azureFunctions, {
|
||||
canPickMany: false,
|
||||
title: constants.selectAzureFunction,
|
||||
ignoreFocusOut: true
|
||||
}));
|
||||
|
||||
if (!azureFunctionName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. select input or output binding
|
||||
const inputOutputItems: (vscode.QuickPickItem & { type: BindingType })[] = [
|
||||
{
|
||||
label: constants.input,
|
||||
type: BindingType.input
|
||||
},
|
||||
{
|
||||
label: constants.output,
|
||||
type: BindingType.output
|
||||
}
|
||||
];
|
||||
|
||||
const selectedBinding = (await vscode.window.showQuickPick(inputOutputItems, {
|
||||
canPickMany: false,
|
||||
title: constants.selectBindingType,
|
||||
ignoreFocusOut: true
|
||||
}));
|
||||
|
||||
if (!selectedBinding) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. ask for object name for the binding
|
||||
const objectName = await vscode.window.showInputBox({
|
||||
prompt: selectedBinding.type === BindingType.input ? constants.sqlTableOrViewToQuery : constants.sqlTableToUpsert,
|
||||
placeHolder: constants.placeHolderObject,
|
||||
validateInput: input => input ? undefined : constants.nameMustNotBeEmpty,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!objectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. ask for connection string setting name
|
||||
let projectUri: vscode.Uri | undefined;
|
||||
try {
|
||||
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
|
||||
}
|
||||
|
||||
let connectionStringSettingName;
|
||||
|
||||
// show the settings from project's local.settings.json if there's an AF functions project
|
||||
if (projectUri) {
|
||||
let settings;
|
||||
try {
|
||||
settings = await azureFunctionsUtils.getLocalSettingsJson(path.join(path.dirname(projectUri.fsPath!), constants.azureFunctionLocalSettingsFileName));
|
||||
} catch (e) {
|
||||
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
let existingSettings: (vscode.QuickPickItem)[] = [];
|
||||
if (settings?.Values) {
|
||||
existingSettings = Object.keys(settings.Values).map(setting => {
|
||||
return {
|
||||
label: setting
|
||||
} as vscode.QuickPickItem;
|
||||
});
|
||||
}
|
||||
|
||||
existingSettings.unshift({ label: constants.createNewLocalAppSettingWithIcon });
|
||||
let sqlConnectionStringSettingExists = existingSettings.find(s => s.label === constants.sqlConnectionStringSetting);
|
||||
|
||||
while (!connectionStringSettingName) {
|
||||
const selectedSetting = await vscode.window.showQuickPick(existingSettings, {
|
||||
canPickMany: false,
|
||||
title: constants.selectSetting,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!selectedSetting) {
|
||||
// User cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedSetting.label === constants.createNewLocalAppSettingWithIcon) {
|
||||
const newConnectionStringSettingName = await vscode.window.showInputBox(
|
||||
{
|
||||
title: constants.enterConnectionStringSettingName,
|
||||
ignoreFocusOut: true,
|
||||
value: sqlConnectionStringSettingExists ? '' : constants.sqlConnectionStringSetting,
|
||||
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;
|
||||
}
|
||||
|
||||
// show the connection string methods (user input and connection profile options)
|
||||
const listOfConnectionStringMethods = [constants.connectionProfile, constants.userConnectionString];
|
||||
while (true) {
|
||||
const selectedConnectionStringMethod = await vscode.window.showQuickPick(listOfConnectionStringMethods, {
|
||||
canPickMany: false,
|
||||
title: constants.selectConnectionString,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!selectedConnectionStringMethod) {
|
||||
// User cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
let connectionString: string = '';
|
||||
let includePassword: string | undefined;
|
||||
let connectionInfo: IConnectionInfo | undefined;
|
||||
let connectionDetails: ConnectionDetails;
|
||||
if (selectedConnectionStringMethod === constants.userConnectionString) {
|
||||
// User chooses to enter connection string manually
|
||||
connectionString = await vscode.window.showInputBox(
|
||||
{
|
||||
title: constants.enterConnectionString,
|
||||
ignoreFocusOut: true,
|
||||
value: 'Server=localhost;Initial Catalog={db_name};User ID=sa;Password={your_password};Persist Security Info=False',
|
||||
validateInput: input => input ? undefined : constants.valueMustNotBeEmpty
|
||||
}
|
||||
) ?? '';
|
||||
} else {
|
||||
// Let user choose from existing connections to create connection string from
|
||||
connectionInfo = await vscodeMssqlApi.promptForConnection(true);
|
||||
if (!connectionInfo) {
|
||||
// User cancelled return to selectedConnectionStringMethod prompt
|
||||
continue;
|
||||
}
|
||||
connectionDetails = { options: connectionInfo };
|
||||
try {
|
||||
// Prompt to include password in connection string if authentication type is SqlLogin and connection has password saved
|
||||
if (connectionInfo.authenticationType === 'SqlLogin' && connectionInfo.password) {
|
||||
includePassword = await vscode.window.showQuickPick([constants.yesString, constants.noString], {
|
||||
title: constants.includePassword,
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (includePassword === constants.yesString) {
|
||||
// set connection string to include password
|
||||
connectionString = await vscodeMssqlApi.getConnectionString(connectionDetails, true, false);
|
||||
}
|
||||
}
|
||||
// set connection string to not include the password if connection info does not include password, or user chooses to not include password, or authentication type is not sql login
|
||||
if (includePassword !== constants.yesString) {
|
||||
connectionString = await vscodeMssqlApi.getConnectionString(connectionDetails, false, false);
|
||||
}
|
||||
} catch (e) {
|
||||
// failed to get connection string for selected connection and will go back to prompt for connection string methods
|
||||
console.warn(e);
|
||||
void vscode.window.showErrorMessage(constants.failedToGetConnectionString);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (connectionString) {
|
||||
try {
|
||||
const projectFolder: string = path.dirname(projectUri.fsPath);
|
||||
const localSettingsPath: string = path.join(projectFolder, constants.azureFunctionLocalSettingsFileName);
|
||||
let userPassword: string | undefined;
|
||||
// Ask user to enter password if auth type is sql login and password is not saved
|
||||
if (connectionInfo?.authenticationType === 'SqlLogin' && !connectionInfo?.password) {
|
||||
userPassword = await vscode.window.showInputBox({
|
||||
prompt: constants.enterPasswordPrompt,
|
||||
placeHolder: constants.enterPasswordManually,
|
||||
ignoreFocusOut: true,
|
||||
password: true,
|
||||
validateInput: input => input ? undefined : constants.valueMustNotBeEmpty
|
||||
});
|
||||
if (userPassword) {
|
||||
// if user enters password replace password placeholder with user entered password
|
||||
connectionString = connectionString.replace(constants.passwordPlaceholder, userPassword);
|
||||
}
|
||||
}
|
||||
if (includePassword !== constants.yesString && !userPassword && connectionInfo?.authenticationType === 'SqlLogin') {
|
||||
// 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) => {
|
||||
if (result === constants.openFile) {
|
||||
// open local.settings.json file
|
||||
void vscode.commands.executeCommand(constants.vscodeOpenCommand, vscode.Uri.file(localSettingsPath));
|
||||
}
|
||||
});
|
||||
}
|
||||
const success = await azureFunctionsUtils.setLocalAppSetting(projectFolder, newConnectionStringSettingName, connectionString);
|
||||
if (success) {
|
||||
// exit both loops and insert binding
|
||||
connectionStringSettingName = newConnectionStringSettingName;
|
||||
break;
|
||||
} else {
|
||||
void vscode.window.showErrorMessage(constants.selectConnectionError());
|
||||
}
|
||||
} catch (e) {
|
||||
// display error message and show select setting quickpick again
|
||||
void vscode.window.showErrorMessage(constants.selectConnectionError(e));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
connectionStringSettingName = selectedSetting.label;
|
||||
}
|
||||
}
|
||||
} 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, connectionStringSettingName);
|
||||
|
||||
if (!result.success) {
|
||||
void vscode.window.showErrorMessage(result.errorMessage);
|
||||
TelemetryReporter.sendErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding);
|
||||
return;
|
||||
}
|
||||
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding)
|
||||
.withAdditionalProperties({ bindingType: selectedBinding.label })
|
||||
.send();
|
||||
} catch (e) {
|
||||
void vscode.window.showErrorMessage(utils.getErrorMessage(e));
|
||||
TelemetryReporter.sendErrorEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.finishAddSqlBinding);
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Add sql extension package reference to project. If the reference is already there, it doesn't get added again
|
||||
await packageHelper.addPackageToAFProjectContainingFile(uri, constants.sqlExtensionPackageName);
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as should from 'should';
|
||||
import * as sinon from 'sinon';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as utils from '../../common/utils';
|
||||
import * as constants from '../../common/constants';
|
||||
import * as azureFunctionUtils from '../../common/azureFunctionsUtils';
|
||||
|
||||
import { createContext, TestContext, createTestCredentials } from '../testContext';
|
||||
import { launchAddSqlBindingQuickpick } from '../../dialogs/addSqlBindingQuickpick';
|
||||
import { PackageHelper } from '../../tools/packageHelper';
|
||||
|
||||
let testContext: TestContext;
|
||||
let packageHelper: PackageHelper;
|
||||
describe('Add SQL Binding quick pick', () => {
|
||||
beforeEach(function (): void {
|
||||
testContext = createContext();
|
||||
packageHelper = new PackageHelper(testContext.outputChannel);
|
||||
});
|
||||
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should show error if the file contains no Azure Functions', async function (): Promise<void> {
|
||||
sinon.stub(utils, 'getAzureFunctionService').resolves(testContext.azureFunctionService.object);
|
||||
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testContext.vscodeMssqlIExtension.object);
|
||||
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||
testContext.azureFunctionService.setup(x => x.getAzureFunctions(TypeMoq.It.isAny())).returns(async () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
azureFunctions: []
|
||||
});
|
||||
});
|
||||
await launchAddSqlBindingQuickpick(vscode.Uri.file('testUri'), packageHelper);
|
||||
|
||||
const msg = constants.noAzureFunctionsInFile;
|
||||
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once');
|
||||
should(spy.calledWith(msg)).be.true(`showErrorMessage not called with expected message '${msg}' Actual '${spy.getCall(0).args[0]}'`);
|
||||
});
|
||||
|
||||
it('Should show error if adding SQL binding was not successful', async function (): Promise<void> {
|
||||
sinon.stub(utils, 'getAzureFunctionService').resolves(testContext.azureFunctionService.object);
|
||||
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testContext.vscodeMssqlIExtension.object);
|
||||
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||
testContext.azureFunctionService.setup(x => x.getAzureFunctions(TypeMoq.It.isAny())).returns(async () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
azureFunctions: ['af1', 'af2']
|
||||
});
|
||||
});
|
||||
//failure since no AFs are found in the project
|
||||
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(undefined);
|
||||
const errormsg = 'Error inserting binding';
|
||||
testContext.azureFunctionService.setup(x => x.addSqlBinding(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
errorMessage: errormsg
|
||||
});
|
||||
});
|
||||
|
||||
// select Azure function
|
||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves({ label: 'af1' });
|
||||
// select input or output binding
|
||||
quickpickStub.onSecondCall().resolves({ label: constants.input });
|
||||
// give object name
|
||||
let inputBoxStub = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dbo.table1');
|
||||
// give connection string setting name
|
||||
inputBoxStub.onSecondCall().resolves('sqlConnectionString');
|
||||
|
||||
await launchAddSqlBindingQuickpick(vscode.Uri.file('testUri'), packageHelper);
|
||||
|
||||
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once');
|
||||
should(spy.calledWith(errormsg)).be.true(`showErrorMessage not called with expected message '${errormsg}' Actual '${spy.getCall(0).args[0]}'`);
|
||||
});
|
||||
|
||||
it('Should show error connection profile does not connect', async function (): Promise<void> {
|
||||
sinon.stub(utils, 'getAzureFunctionService').resolves(testContext.azureFunctionService.object);
|
||||
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testContext.vscodeMssqlIExtension.object);
|
||||
let connectionCreds = createTestCredentials();
|
||||
|
||||
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(vscode.Uri.file('testUri'));
|
||||
testContext.azureFunctionService.setup(x => x.getAzureFunctions(TypeMoq.It.isAny())).returns(async () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
azureFunctions: ['af1']
|
||||
});
|
||||
});
|
||||
|
||||
// Mocks connect call to mssql
|
||||
let error = new Error('Connection Request Failed');
|
||||
testContext.vscodeMssqlIExtension.setup(x => x.connect(TypeMoq.It.isAny(), undefined)).throws(error);
|
||||
|
||||
// Mocks promptForConnection
|
||||
testContext.vscodeMssqlIExtension.setup(x => x.promptForConnection(true)).returns(() => Promise.resolve(connectionCreds));
|
||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
||||
// select Azure function
|
||||
quickpickStub.onFirstCall().resolves({ label: 'af1' });
|
||||
// select input or output binding
|
||||
quickpickStub.onSecondCall().resolves({ label: constants.input });
|
||||
|
||||
// give object name
|
||||
let inputBoxStub = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dbo.table1');
|
||||
|
||||
// select connection profile
|
||||
quickpickStub.onThirdCall().resolves({ label: constants.createNewLocalAppSettingWithIcon });
|
||||
|
||||
// give connection string setting name
|
||||
inputBoxStub.onSecondCall().resolves('SqlConnectionString');
|
||||
|
||||
// select connection profile method
|
||||
quickpickStub.onCall(3).resolves({ label: constants.connectionProfile });
|
||||
|
||||
await launchAddSqlBindingQuickpick(vscode.Uri.file('testUri'), packageHelper);
|
||||
|
||||
// should go back to the select connection string methods
|
||||
should(quickpickStub.callCount === 5);
|
||||
should(quickpickStub.getCall(4).args).deepEqual([
|
||||
[constants.connectionProfile, constants.userConnectionString],
|
||||
{
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true,
|
||||
title: constants.selectConnectionString
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as should from 'should';
|
||||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import * as sinon from 'sinon';
|
||||
import * as constants from '../common/constants';
|
||||
import * as azureFunctionUtils from '../common/azureFunctionsUtils';
|
||||
import { PackageHelper } from '../tools/packageHelper';
|
||||
import { createContext, TestContext } from './testContext';
|
||||
|
||||
let testContext: TestContext;
|
||||
let packageHelper: PackageHelper;
|
||||
|
||||
describe('PackageHelper tests', function (): void {
|
||||
beforeEach(function (): void {
|
||||
testContext = createContext();
|
||||
packageHelper = new PackageHelper(testContext.outputChannel);
|
||||
});
|
||||
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should construct correct add Package Arguments', function (): void {
|
||||
const packageHelper = new PackageHelper( vscode.window.createOutputChannel('db project test'));
|
||||
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`);
|
||||
}
|
||||
else {
|
||||
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 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}`);
|
||||
}
|
||||
else {
|
||||
should(result).equal(` add "/dummy/project/path.csproj" package ${constants.sqlExtensionPackageName} -v ${constants.VersionNumber}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should show info message to add sql bindings package if project is not found', async function (): Promise<void> {
|
||||
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(undefined);
|
||||
const spy = sinon.spy(vscode.window, 'showInformationMessage');
|
||||
|
||||
await packageHelper.addPackageToAFProjectContainingFile(vscode.Uri.file(''), constants.sqlExtensionPackageName);
|
||||
should(spy.calledOnce).be.true('showInformationMessage should have been called exactly once');
|
||||
});
|
||||
});
|
||||
@@ -8,16 +8,11 @@ import * as azdata from 'azdata';
|
||||
import * as path from 'path';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as mssql from '../../../mssql/src/mssql';
|
||||
import * as vscodeMssql from 'vscode-mssql';
|
||||
|
||||
export interface TestContext {
|
||||
context: vscode.ExtensionContext;
|
||||
dacFxService: TypeMoq.IMock<mssql.IDacFxService>;
|
||||
azureFunctionService: TypeMoq.IMock<vscodeMssql.IAzureFunctionsService>;
|
||||
outputChannel: vscode.OutputChannel;
|
||||
vscodeMssqlIExtension: TypeMoq.IMock<vscodeMssql.IExtension>
|
||||
dacFxMssqlService: TypeMoq.IMock<vscodeMssql.IDacFxService>;
|
||||
schemaCompareService: TypeMoq.IMock<vscodeMssql.ISchemaCompareService>;
|
||||
}
|
||||
|
||||
export const mockDacFxResult = {
|
||||
@@ -124,153 +119,6 @@ export class MockDacFxService implements mssql.IDacFxService {
|
||||
public validateStreamingJob(_: string, __: string): Thenable<mssql.ValidateStreamingJobResult> { return Promise.resolve(mockDacFxResult); }
|
||||
}
|
||||
|
||||
export const mockResultStatus = {
|
||||
success: true,
|
||||
errorMessage: ''
|
||||
};
|
||||
|
||||
export const mockGetAzureFunctionsResult = {
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
azureFunctions: []
|
||||
};
|
||||
|
||||
export class MockAzureFunctionService implements vscodeMssql.IAzureFunctionsService {
|
||||
addSqlBinding(_: vscodeMssql.BindingType, __: string, ___: string, ____: string, _____: string): Thenable<vscodeMssql.ResultStatus> { return Promise.resolve(mockResultStatus); }
|
||||
getAzureFunctions(_: string): Thenable<vscodeMssql.GetAzureFunctionsResult> { return Promise.resolve(mockGetAzureFunctionsResult); }
|
||||
}
|
||||
|
||||
export const mockDacFxMssqlOptionResult: vscodeMssql.DacFxOptionsResult = {
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
deploymentOptions: {
|
||||
ignoreTableOptions: false,
|
||||
ignoreSemicolonBetweenStatements: false,
|
||||
ignoreRouteLifetime: false,
|
||||
ignoreRoleMembership: false,
|
||||
ignoreQuotedIdentifiers: false,
|
||||
ignorePermissions: false,
|
||||
ignorePartitionSchemes: false,
|
||||
ignoreObjectPlacementOnPartitionScheme: false,
|
||||
ignoreNotForReplication: false,
|
||||
ignoreLoginSids: false,
|
||||
ignoreLockHintsOnIndexes: false,
|
||||
ignoreKeywordCasing: false,
|
||||
ignoreIndexPadding: false,
|
||||
ignoreIndexOptions: false,
|
||||
ignoreIncrement: false,
|
||||
ignoreIdentitySeed: false,
|
||||
ignoreUserSettingsObjects: false,
|
||||
ignoreFullTextCatalogFilePath: false,
|
||||
ignoreWhitespace: false,
|
||||
ignoreWithNocheckOnForeignKeys: false,
|
||||
verifyCollationCompatibility: false,
|
||||
unmodifiableObjectWarnings: false,
|
||||
treatVerificationErrorsAsWarnings: false,
|
||||
scriptRefreshModule: false,
|
||||
scriptNewConstraintValidation: false,
|
||||
scriptFileSize: false,
|
||||
scriptDeployStateChecks: false,
|
||||
scriptDatabaseOptions: false,
|
||||
scriptDatabaseCompatibility: false,
|
||||
scriptDatabaseCollation: false,
|
||||
runDeploymentPlanExecutors: false,
|
||||
registerDataTierApplication: false,
|
||||
populateFilesOnFileGroups: false,
|
||||
noAlterStatementsToChangeClrTypes: false,
|
||||
includeTransactionalScripts: false,
|
||||
includeCompositeObjects: false,
|
||||
allowUnsafeRowLevelSecurityDataMovement: false,
|
||||
ignoreWithNocheckOnCheckConstraints: false,
|
||||
ignoreFillFactor: false,
|
||||
ignoreFileSize: false,
|
||||
ignoreFilegroupPlacement: false,
|
||||
doNotAlterReplicatedObjects: false,
|
||||
doNotAlterChangeDataCaptureObjects: false,
|
||||
disableAndReenableDdlTriggers: false,
|
||||
deployDatabaseInSingleUserMode: false,
|
||||
createNewDatabase: false,
|
||||
compareUsingTargetCollation: false,
|
||||
commentOutSetVarDeclarations: false,
|
||||
blockWhenDriftDetected: false,
|
||||
blockOnPossibleDataLoss: false,
|
||||
backupDatabaseBeforeChanges: false,
|
||||
allowIncompatiblePlatform: false,
|
||||
allowDropBlockingAssemblies: false,
|
||||
dropConstraintsNotInSource: false,
|
||||
dropDmlTriggersNotInSource: false,
|
||||
dropExtendedPropertiesNotInSource: false,
|
||||
dropIndexesNotInSource: false,
|
||||
ignoreFileAndLogFilePath: false,
|
||||
ignoreExtendedProperties: false,
|
||||
ignoreDmlTriggerState: false,
|
||||
ignoreDmlTriggerOrder: false,
|
||||
ignoreDefaultSchema: false,
|
||||
ignoreDdlTriggerState: false,
|
||||
ignoreDdlTriggerOrder: false,
|
||||
ignoreCryptographicProviderFilePath: false,
|
||||
verifyDeployment: false,
|
||||
ignoreComments: false,
|
||||
ignoreColumnCollation: false,
|
||||
ignoreAuthorizer: false,
|
||||
ignoreAnsiNulls: false,
|
||||
generateSmartDefaults: false,
|
||||
dropStatisticsNotInSource: false,
|
||||
dropRoleMembersNotInSource: false,
|
||||
dropPermissionsNotInSource: false,
|
||||
dropObjectsNotInSource: false,
|
||||
ignoreColumnOrder: false,
|
||||
doNotDropObjectTypes: [],
|
||||
excludeObjectTypes: []
|
||||
}
|
||||
};
|
||||
|
||||
export class MockDacFxMssqlService implements vscodeMssql.IDacFxService {
|
||||
public exportBacpac(_: string, __: string, ___: string, ____: vscodeMssql.TaskExecutionMode): Thenable<vscodeMssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public importBacpac(_: string, __: string, ___: string, ____: vscodeMssql.TaskExecutionMode): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public extractDacpac(_: string, __: string, ___: string, ____: string, _____: string, ______: vscodeMssql.TaskExecutionMode): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public createProjectFromDatabase(_: string, __: string, ___: string, ____: string, _____: string, ______: vscodeMssql.ExtractTarget, _______: vscodeMssql.TaskExecutionMode): Thenable<vscodeMssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public deployDacpac(_: string, __: string, ___: boolean, ____: string, _____: vscodeMssql.TaskExecutionMode, ______?: Record<string, string>): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public generateDeployScript(_: string, __: string, ___: string, ____: vscodeMssql.TaskExecutionMode, ______?: Record<string, string>): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public generateDeployPlan(_: string, __: string, ___: string, ____: vscodeMssql.TaskExecutionMode): Thenable<vscodeMssql.GenerateDeployPlanResult> { return Promise.resolve(mockDacFxResult); }
|
||||
public getOptionsFromProfile(_: string): Thenable<vscodeMssql.DacFxOptionsResult> { return Promise.resolve(mockDacFxMssqlOptionResult); }
|
||||
public validateStreamingJob(_: string, __: string): Thenable<mssql.ValidateStreamingJobResult> { return Promise.resolve(mockDacFxResult); }
|
||||
}
|
||||
|
||||
export class MockSchemaCompareService implements vscodeMssql.ISchemaCompareService {
|
||||
schemaCompareGetDefaultOptions(): Thenable<vscodeMssql.SchemaCompareOptionsResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockVscodeMssqlIExtension implements vscodeMssql.IExtension {
|
||||
sqlToolsServicePath: string = '';
|
||||
dacFx: vscodeMssql.IDacFxService;
|
||||
schemaCompare: vscodeMssql.ISchemaCompareService;
|
||||
azureFunctions: vscodeMssql.IAzureFunctionsService;
|
||||
|
||||
constructor() {
|
||||
this.dacFx = new MockDacFxMssqlService;
|
||||
this.schemaCompare = new MockSchemaCompareService;
|
||||
this.azureFunctions = new MockAzureFunctionService;
|
||||
}
|
||||
promptForConnection(_?: boolean): Promise<vscodeMssql.IConnectionInfo | undefined> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
connect(_: vscodeMssql.IConnectionInfo, __?: boolean): Promise<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
listDatabases(_: string): Promise<string[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getDatabaseNameFromTreeNode(_: vscodeMssql.ITreeNodeInfo): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getConnectionString(_: string | vscodeMssql.ConnectionDetails, ___?: boolean, _____?: boolean): Promise<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export function createContext(): TestContext {
|
||||
let extensionPath = path.join(__dirname, '..', '..');
|
||||
|
||||
@@ -303,10 +151,6 @@ export function createContext(): TestContext {
|
||||
extension: undefined as any
|
||||
},
|
||||
dacFxService: TypeMoq.Mock.ofType(MockDacFxService),
|
||||
azureFunctionService: TypeMoq.Mock.ofType(MockAzureFunctionService),
|
||||
vscodeMssqlIExtension: TypeMoq.Mock.ofType(MockVscodeMssqlIExtension),
|
||||
dacFxMssqlService: TypeMoq.Mock.ofType(MockDacFxMssqlService),
|
||||
schemaCompareService: TypeMoq.Mock.ofType(MockSchemaCompareService),
|
||||
outputChannel: {
|
||||
name: '',
|
||||
append: () => { },
|
||||
@@ -342,41 +186,3 @@ export const mockConnectionProfile: azdata.IConnectionProfile = {
|
||||
connectionName: 'My Connection Name'
|
||||
}
|
||||
};
|
||||
|
||||
export function createTestCredentials(): vscodeMssql.IConnectionInfo {
|
||||
const creds: vscodeMssql.IConnectionInfo = {
|
||||
server: 'my-server',
|
||||
database: 'my_db',
|
||||
user: 'sa',
|
||||
password: '12345678',
|
||||
email: 'test-email',
|
||||
accountId: 'test-account-id',
|
||||
port: 1234,
|
||||
authenticationType: 'test',
|
||||
azureAccountToken: '',
|
||||
expiresOn: 0,
|
||||
encrypt: false,
|
||||
trustServerCertificate: false,
|
||||
persistSecurityInfo: false,
|
||||
connectTimeout: 15,
|
||||
connectRetryCount: 0,
|
||||
connectRetryInterval: 0,
|
||||
applicationName: 'vscode-mssql',
|
||||
workstationId: 'test',
|
||||
applicationIntent: '',
|
||||
currentLanguage: '',
|
||||
pooling: true,
|
||||
maxPoolSize: 15,
|
||||
minPoolSize: 0,
|
||||
loadBalanceTimeout: 0,
|
||||
replication: false,
|
||||
attachDbFilename: '',
|
||||
failoverPartner: '',
|
||||
multiSubnetFailover: false,
|
||||
multipleActiveResultSets: false,
|
||||
packetSize: 8192,
|
||||
typeSystemVersion: 'Latest',
|
||||
connectionString: ''
|
||||
};
|
||||
return creds;
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as utils from '../common/utils';
|
||||
import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
|
||||
import * as constants from '../common/constants';
|
||||
import { NetCoreTool } from './netcoreTool';
|
||||
import { ShellCommandOptions } from './shellExecutionHelper';
|
||||
|
||||
export class PackageHelper {
|
||||
private netCoreTool: NetCoreTool;
|
||||
private _outputChannel: vscode.OutputChannel;
|
||||
|
||||
constructor(outputChannel: vscode.OutputChannel) {
|
||||
this.netCoreTool = new NetCoreTool(outputChannel);
|
||||
this._outputChannel = outputChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the parameters for a dotnet add package
|
||||
* @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(projectUri: vscode.Uri, packageName: string, packageVersion?: string): string {
|
||||
const projectPath = utils.getQuotedPath(projectUri.fsPath);
|
||||
if (packageVersion) {
|
||||
return ` add ${projectPath} package ${packageName} -v ${packageVersion}`;
|
||||
} else {
|
||||
// pull in the latest version of the package and allow prerelease versions
|
||||
return ` add ${projectPath} package ${packageName} --prerelease`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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
|
||||
*/
|
||||
public async addPackage(projectUri: vscode.Uri, packageName: string, packageVersion?: string): Promise<void> {
|
||||
const addOptions: ShellCommandOptions = {
|
||||
commandTitle: constants.addPackage,
|
||||
argument: this.constructAddPackageArguments(projectUri, packageName, packageVersion)
|
||||
};
|
||||
// Add package can be done with any version of .NET so skip the supported version check
|
||||
await this.netCoreTool.runDotnetCommand(addOptions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds specified package to Azure Functions project the specified file is a part of
|
||||
* @param fileUri 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(fileUri: vscode.Uri, packageName: string, packageVersion?: string): Promise<void> {
|
||||
try {
|
||||
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
|
||||
// user to make sure their project has the package reference
|
||||
if (project) {
|
||||
await this.addPackage(project, packageName, packageVersion);
|
||||
} else {
|
||||
void vscode.window.showInformationMessage(constants.addPackageReferenceMessage, constants.moreInformation).then((result) => {
|
||||
if (result === constants.moreInformation) {
|
||||
void vscode.env.openExternal(vscode.Uri.parse(constants.sqlBindingsHelpLink));
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
const result = await vscode.window.showErrorMessage(constants.addSqlBindingPackageError, constants.checkoutOutputMessage);
|
||||
if (result === constants.checkoutOutputMessage) {
|
||||
this._outputChannel.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user