diff --git a/extensions/sql-bindings/package.json b/extensions/sql-bindings/package.json index 80d43164c4..32d8d10bc3 100644 --- a/extensions/sql-bindings/package.json +++ b/extensions/sql-bindings/package.json @@ -63,7 +63,6 @@ "dependencies": { "@microsoft/ads-extension-telemetry": "^1.1.5", "fast-glob": "^3.2.7", - "jsonc-parser": "^2.3.1", "promisify-child-process": "^3.1.1", "vscode-nls": "^4.1.2", "vscode-languageclient": "5.2.1" diff --git a/extensions/sql-bindings/src/common/azureFunctionsUtils.ts b/extensions/sql-bindings/src/common/azureFunctionsUtils.ts index 0f4ae00b0d..6c6af956a6 100644 --- a/extensions/sql-bindings/src/common/azureFunctionsUtils.ts +++ b/extensions/sql-bindings/src/common/azureFunctionsUtils.ts @@ -5,10 +5,10 @@ import * as os from 'os'; import * as fs from 'fs'; import * as vscode from 'vscode'; +import * as path from 'path'; import * as utils from './utils'; import * as constants from './constants'; import { BindingType } from 'sql-bindings'; -import * as path from 'path'; import { ConnectionDetails, IConnectionInfo } from 'vscode-mssql'; // https://github.com/microsoft/vscode-azurefunctions/blob/main/src/vscode-azurefunctions.api.d.ts import { AzureFunctionsExtensionApi } from '../typings/vscode-azurefunctions.api'; diff --git a/extensions/sql-bindings/src/common/parseJson.ts b/extensions/sql-bindings/src/common/parseJson.ts deleted file mode 100644 index de4c00db29..0000000000 --- a/extensions/sql-bindings/src/common/parseJson.ts +++ /dev/null @@ -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(data: string): T { - if (data.charCodeAt(0) === 0xFEFF) { - data = data.slice(1); - } - - const errors: jsonc.ParseError[] = []; - const result: 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]; -} diff --git a/extensions/sql-bindings/src/common/utils.ts b/extensions/sql-bindings/src/common/utils.ts index 78cc9b7034..bfd6309ae4 100644 --- a/extensions/sql-bindings/src/common/utils.ts +++ b/extensions/sql-bindings/src/common/utils.ts @@ -32,16 +32,6 @@ export function getErrorMessage(error: any): string { : typeof error === 'string' ? error : `${JSON.stringify(error, undefined, '\t')}`; } -export async function getAzureFunctionService(): Promise { - 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 { const ext = vscode.extensions.getExtension(vscodeMssql.extension.name) as vscode.Extension; return ext.activate(); diff --git a/extensions/sql-bindings/src/dialogs/addSqlBindingQuickpick.ts b/extensions/sql-bindings/src/dialogs/addSqlBindingQuickpick.ts index d513d61bae..a7b3e10df0 100644 --- a/extensions/sql-bindings/src/dialogs/addSqlBindingQuickpick.ts +++ b/extensions/sql-bindings/src/dialogs/addSqlBindingQuickpick.ts @@ -8,6 +8,7 @@ import * as constants from '../common/constants'; import * as utils from '../common/utils'; import * as azureFunctionsUtils from '../common/azureFunctionsUtils'; import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry'; +import { addSqlBinding, getAzureFunctions } from '../services/azureFunctionsService'; export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined): Promise { TelemetryReporter.sendActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.startAddSqlBinding); @@ -27,16 +28,16 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined): } } - // get all the Azure functions in the file - const azureFunctionsService = await utils.getAzureFunctionService(); + // get azure functions from STS request let getAzureFunctionsResult; try { - getAzureFunctionsResult = await azureFunctionsService.getAzureFunctions(uri.fsPath); + getAzureFunctionsResult = await getAzureFunctions(uri.fsPath); } catch (e) { void vscode.window.showErrorMessage(utils.getErrorMessage(e)); return; } + // get all the Azure functions in the file const azureFunctions = getAzureFunctionsResult.azureFunctions; if (azureFunctions.length === 0) { @@ -84,7 +85,7 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined): // 5. insert binding try { - const result = await azureFunctionsService.addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName); + const result = await addSqlBinding(selectedBinding.type, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName); if (!result.success) { void vscode.window.showErrorMessage(result.errorMessage); diff --git a/extensions/sql-bindings/src/services/azureFunctionsService.ts b/extensions/sql-bindings/src/services/azureFunctionsService.ts index ef62eee246..9cbb76dc09 100644 --- a/extensions/sql-bindings/src/services/azureFunctionsService.ts +++ b/extensions/sql-bindings/src/services/azureFunctionsService.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as utils from '../common/utils'; -import * as azureFunctionUtils from '../common/azureFunctionsUtils'; +import * as azureFunctionsUtils from '../common/azureFunctionsUtils'; import * as constants from '../common/constants'; import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts'; import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings'; @@ -15,12 +15,12 @@ export const hostFileName: string = 'host.json'; export async function createAzureFunction(connectionString: string, schema: string, table: string): Promise { - const azureFunctionApi = await azureFunctionUtils.getAzureFunctionsExtensionApi(); + const azureFunctionApi = await azureFunctionsUtils.getAzureFunctionsExtensionApi(); if (!azureFunctionApi) { return; } - let projectFile = await azureFunctionUtils.getAzureFunctionProject(); - let newHostProjectFile!: azureFunctionUtils.IFileFunctionObject; + let projectFile = await azureFunctionsUtils.getAzureFunctionProject(); + let newHostProjectFile!: azureFunctionsUtils.IFileFunctionObject; let hostFile: string; if (!projectFile) { @@ -34,13 +34,13 @@ export async function createAzureFunction(connectionString: string, schema: stri try { // because of an AF extension API issue, we have to get the newly created file by adding a watcher // issue: https://github.com/microsoft/vscode-azurefunctions/issues/3052 - newHostProjectFile = await azureFunctionUtils.waitForNewHostFile(); + newHostProjectFile = await azureFunctionsUtils.waitForNewHostFile(); await azureFunctionApi.createFunction({}); const timeoutForHostFile = utils.timeoutPromise(constants.timeoutProjectError); hostFile = await Promise.race([newHostProjectFile.filePromise, timeoutForHostFile]); if (hostFile) { // start the add sql binding flow - projectFile = await azureFunctionUtils.getAzureFunctionProject(); + projectFile = await azureFunctionsUtils.getAzureFunctionProject(); } } catch (error) { void vscode.window.showErrorMessage(utils.formatString(constants.errorNewAzureFunction, error.message ?? error)); @@ -54,7 +54,7 @@ export async function createAzureFunction(connectionString: string, schema: stri if (projectFile) { // because of an AF extension API issue, we have to get the newly created file by adding a watcher // issue: https://github.com/microsoft/vscode-azurefunctions/issues/2908 - const newFunctionFileObject = azureFunctionUtils.waitForNewFunctionFile(projectFile); + const newFunctionFileObject = azureFunctionsUtils.waitForNewFunctionFile(projectFile); let functionFile: string; let functionName: string; @@ -87,33 +87,17 @@ export async function createAzureFunction(connectionString: string, schema: stri } // 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 - }); + const selectedBinding = await azureFunctionsUtils.promptForBindingType(); if (!selectedBinding) { return; } - await azureFunctionUtils.addNugetReferenceToProjectFile(projectFile); - await azureFunctionUtils.addConnectionStringToConfig(connectionString, projectFile); + await azureFunctionsUtils.addNugetReferenceToProjectFile(projectFile); + await azureFunctionsUtils.addConnectionStringToConfig(connectionString, projectFile); let objectName = utils.generateQuotedFullName(schema, table); - const azureFunctionsService = await utils.getAzureFunctionService(); - await azureFunctionsService.addSqlBinding( + await addSqlBinding( selectedBinding.type, functionFile, functionName, @@ -121,7 +105,7 @@ export async function createAzureFunction(connectionString: string, schema: stri constants.sqlConnectionString ); - azureFunctionUtils.overwriteAzureFunctionMethodBody(functionFile); + azureFunctionsUtils.overwriteAzureFunctionMethodBody(functionFile); } } @@ -154,7 +138,6 @@ export async function addSqlBinding( return vscodeMssqlApi.sendRequest(azureFunctionsContracts.AddSqlBindingRequest.type, params); } - /** * Gets the names of the Azure functions in the file * @param filePath Path of the file to get the Azure functions diff --git a/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts b/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts index 0307e9aec7..df7dcbe805 100644 --- a/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts +++ b/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts @@ -10,11 +10,13 @@ import * as TypeMoq from 'typemoq'; import * as utils from '../../common/utils'; import * as constants from '../../common/constants'; import * as azureFunctionUtils from '../../common/azureFunctionsUtils'; +import * as azureFunctionService from '../../services/azureFunctionsService'; import { createTestUtils, TestUtils, createTestCredentials } from '../testUtils'; import { launchAddSqlBindingQuickpick } from '../../dialogs/addSqlBindingQuickpick'; let testUtils: TestUtils; +const fileUri = vscode.Uri.file('testUri'); describe('Add SQL Binding quick pick', () => { beforeEach(function (): void { testUtils = createTestUtils(); @@ -25,17 +27,16 @@ describe('Add SQL Binding quick pick', () => { }); it('Should show error if the file contains no Azure Functions', async function (): Promise { - sinon.stub(utils, 'getAzureFunctionService').resolves(testUtils.azureFunctionService.object); sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); - const spy = sinon.spy(vscode.window, 'showErrorMessage'); - testUtils.azureFunctionService.setup(x => x.getAzureFunctions(TypeMoq.It.isAny())).returns(async () => { - return Promise.resolve({ + sinon.stub(azureFunctionService, 'getAzureFunctions').withArgs(fileUri.fsPath).returns( + Promise.resolve({ success: true, errorMessage: '', azureFunctions: [] - }); - }); - await launchAddSqlBindingQuickpick(vscode.Uri.file('testUri')); + })); + const spy = sinon.spy(vscode.window, 'showErrorMessage'); + + await launchAddSqlBindingQuickpick(fileUri); const msg = constants.noAzureFunctionsInFile; should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once'); @@ -43,25 +44,24 @@ describe('Add SQL Binding quick pick', () => { }); it('Should show error if adding SQL binding was not successful', async function (): Promise { - sinon.stub(utils, 'getAzureFunctionService').resolves(testUtils.azureFunctionService.object); sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); - const spy = sinon.spy(vscode.window, 'showErrorMessage'); - testUtils.azureFunctionService.setup(x => x.getAzureFunctions(TypeMoq.It.isAny())).returns(async () => { - return Promise.resolve({ + sinon.stub(azureFunctionService, 'getAzureFunctions').withArgs(fileUri.fsPath).returns( + 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'; - testUtils.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 - }); - }); + sinon.stub(azureFunctionService, 'addSqlBinding').withArgs( + sinon.match.any, sinon.match.any, sinon.match.any, + sinon.match.any, sinon.match.any).returns( + Promise.resolve({ + success: false, + errorMessage: errormsg + })); + const spy = sinon.spy(vscode.window, 'showErrorMessage'); // select Azure function let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves({ label: 'af1' }); @@ -79,18 +79,16 @@ describe('Add SQL Binding quick pick', () => { }); it('Should show error connection profile does not connect', async function (): Promise { - sinon.stub(utils, 'getAzureFunctionService').resolves(testUtils.azureFunctionService.object); sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); let connectionCreds = createTestCredentials(); sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(vscode.Uri.file('testUri')); - testUtils.azureFunctionService.setup(x => x.getAzureFunctions(TypeMoq.It.isAny())).returns(async () => { - return Promise.resolve({ + sinon.stub(azureFunctionService, 'getAzureFunctions').withArgs(fileUri.fsPath).returns( + Promise.resolve({ success: true, errorMessage: '', azureFunctions: ['af1'] - }); - }); + })); // Mocks connect call to mssql let error = new Error('Connection Request Failed'); diff --git a/extensions/sql-bindings/src/test/testUtils.ts b/extensions/sql-bindings/src/test/testUtils.ts index a60dc26f46..673a979471 100644 --- a/extensions/sql-bindings/src/test/testUtils.ts +++ b/extensions/sql-bindings/src/test/testUtils.ts @@ -10,12 +10,10 @@ import * as TypeMoq from 'typemoq'; import * as mssql from '../../../mssql/src/mssql'; import * as vscodeMssql from 'vscode-mssql'; import { RequestType } from 'vscode-languageclient'; -import { BindingType, GetAzureFunctionsResult } from 'sql-bindings'; export interface TestUtils { context: vscode.ExtensionContext; dacFxService: TypeMoq.IMock; - azureFunctionService: TypeMoq.IMock; outputChannel: vscode.OutputChannel; vscodeMssqlIExtension: TypeMoq.IMock dacFxMssqlService: TypeMoq.IMock; @@ -131,17 +129,6 @@ export const mockResultStatus = { errorMessage: '' }; -export const mockGetAzureFunctionsResult = { - success: true, - errorMessage: '', - azureFunctions: [] -}; - -export class MockAzureFunctionService implements vscodeMssql.IAzureFunctionsService { - addSqlBinding(_: BindingType, __: string, ___: string, ____: string, _____: string): Thenable { return Promise.resolve(mockResultStatus); } - getAzureFunctions(_: string): Thenable { return Promise.resolve(mockGetAzureFunctionsResult); } -} - export const mockDacFxMssqlOptionResult: vscodeMssql.DacFxOptionsResult = { success: true, errorMessage: '', @@ -249,12 +236,10 @@ 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; } sendRequest(_: RequestType, __?: P): Promise { throw new Error('Method not implemented.'); @@ -311,7 +296,6 @@ export function createTestUtils(): TestUtils { 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), diff --git a/extensions/sql-bindings/src/typings/vscode-mssql.d.ts b/extensions/sql-bindings/src/typings/vscode-mssql.d.ts index 0bc49169cc..84045315bb 100644 --- a/extensions/sql-bindings/src/typings/vscode-mssql.d.ts +++ b/extensions/sql-bindings/src/typings/vscode-mssql.d.ts @@ -7,7 +7,6 @@ declare module 'vscode-mssql' { import * as vscode from 'vscode'; import { RequestType } from 'vscode-languageclient'; - import { BindingType, GetAzureFunctionsResult } from 'sql-bindings'; /** * Covers defining what the vscode-mssql extension exports to other extensions @@ -41,11 +40,6 @@ declare module 'vscode-mssql' { */ readonly schemaCompare: ISchemaCompareService; - /** - * Service for accessing AzureFunctions functionality - */ - readonly azureFunctions: IAzureFunctionsService; - /** * Prompts the user to select an existing connection or create a new one, and then returns the result * @param ignoreFocusOut Whether the quickpick prompt ignores focus out (default false) @@ -101,7 +95,7 @@ declare module 'vscode-mssql' { * @param params The params to pass with the request * @returns A promise object for when the request receives a response */ - sendRequest(requestType: RequestType, params?: P): Promise; + sendRequest(requestType: RequestType, params?: P): Promise; } /** @@ -302,24 +296,6 @@ declare module 'vscode-mssql' { validateStreamingJob(packageFilePath: string, createStreamingJobTsql: string): Thenable; } - export interface IAzureFunctionsService { - /** - * Adds a SQL Binding to a specified Azure function in a file - * @param bindingType Type of SQL Binding - * @param filePath Path of the file where the Azure Functions are - * @param functionName Name of the function where the SQL Binding is to be added - * @param objectName Name of Object for the SQL Query - * @param connectionStringSetting Setting for the connection string - */ - addSqlBinding(bindingType: BindingType, filePath: string, functionName: string, objectName: string, connectionStringSetting: string): Thenable; - /** - * Gets the names of the Azure functions in the file - * @param filePath Path of the file to get the Azure functions - * @returns array of names of Azure functions in the file - */ - getAzureFunctions(filePath: string): Thenable; - } - export const enum TaskExecutionMode { execute = 0, script = 1, diff --git a/extensions/sql-bindings/yarn.lock b/extensions/sql-bindings/yarn.lock index 9ad9c2c51e..bce5b1255e 100644 --- a/extensions/sql-bindings/yarn.lock +++ b/extensions/sql-bindings/yarn.lock @@ -794,11 +794,6 @@ 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== - just-extend@^4.0.2: version "4.2.1" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"