diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 459b2bec6..7617b4676 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -31,6 +31,7 @@ dependencies: applicationinsights: 1.7.3 argparse: 1.0.10 axios: 0.21.1 + axios-https-proxy: 0.1.1_axios@0.21.1 botbuilder-lg: 4.12.0 botframework-schema: 4.7.2 camelcase: 4.1.0 @@ -1263,6 +1264,15 @@ packages: dev: false resolution: integrity: sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + /axios-https-proxy/0.1.1_axios@0.21.1: + dependencies: + axios: 0.21.1 + https-proxy-agent: 2.2.4 + dev: false + peerDependencies: + axios: ^0.18.0 + resolution: + integrity: sha512-slFaor1FTRhO6V6r2ewuWLBRIF33Lkg+wRmUVJPWRe0JRgBKevBSlgjDInp+AeKGKZjyT7k3l44l67CvEme1Cg== /axios/0.21.1: dependencies: follow-redirects: 1.13.0 @@ -7400,6 +7410,8 @@ packages: '@types/node': 10.17.17 '@types/node-fetch': 2.5.5 antlr4: 4.8.0 + axios: 0.21.1 + axios-https-proxy: 0.1.1_axios@0.21.1 chai: 4.2.0 chalk: 2.4.1 console-stream: 0.1.1 @@ -7424,7 +7436,7 @@ packages: dev: false name: '@rush-temp/bf-lu' resolution: - integrity: sha512-idgtiuhAkB8CwVNp0iQcGkBUCIbsmQab6zQujW1T1AOUwPZjE1Aml1y9NUizV4G5batuOhygJvtZHcYB4UZrKQ== + integrity: sha512-YEzrI4a1MkTCI9DAKeOSjWkcY7ZZGwxramK3Ui5r93OZKknL+Bo7V3Y1NZoVl4B5IxdWobd3eurR2707dFD83w== tarball: 'file:projects/bf-lu.tgz' version: 0.0.0 'file:projects/bf-luis-cli.tgz': @@ -7468,7 +7480,7 @@ packages: dev: false name: '@rush-temp/bf-luis-cli' resolution: - integrity: sha512-fg5B1E3kaqUNQDT45ZWxBSAHE/gNVmIAbz2VCKEcK1/xK7DYLvcGlX5bFMC4AaYw7vSnJZ4Q5S3JYugPG6TinQ== + integrity: sha512-hi+iOx5Yq08FxsCiGuAIZzdB1/4wcaP/qnDaXfBNMsoT1AB/hjHydm0OyRR/QtBwjFAxMzUCafPmHKqD3JdhPw== tarball: 'file:projects/bf-luis-cli.tgz' version: 0.0.0 'file:projects/bf-orchestrator-cli.tgz': @@ -7582,7 +7594,7 @@ packages: dev: false name: '@rush-temp/bf-qnamaker' resolution: - integrity: sha512-XZ3HKrN0Wmk+5WLyi9NfLxhOMPTkQN5R0H+Js+0vM2jaq00NmpGinUalQTDvYrJrgaTIsentVSedsEUHyUU/JQ== + integrity: sha512-4493Sz8sTpkTWkPegzzpmpFadoboXwalx1Yj9hLwJLuw/csHob4JzWS0Ota83j6lWkfy0NtObDUb7GJrZtN5xg== tarball: 'file:projects/bf-qnamaker.tgz' version: 0.0.0 'file:projects/botframework-cli.tgz': @@ -7655,6 +7667,7 @@ specifiers: applicationinsights: ^1.0.8 argparse: ~1.0.10 axios: ~0.21.1 + axios-https-proxy: ^0.1.1 botbuilder-lg: 4.12.0 botframework-schema: ^4.5.1 camelcase: ^4.1.0 diff --git a/packages/dialog/src/library/schemaMerger.ts b/packages/dialog/src/library/schemaMerger.ts index 6cd7418c2..0934ea1c2 100644 --- a/packages/dialog/src/library/schemaMerger.ts +++ b/packages/dialog/src/library/schemaMerger.ts @@ -1424,6 +1424,7 @@ export class SchemaMerger { for (const [ext, files] of this.files.entries()) { for (const [file, records] of files.entries()) { const winner = records[0] + let winnerSrc = '' const same: PathComponent[] = [] const conflicts: PathComponent[] = [] for (const alt of records) { @@ -1431,18 +1432,24 @@ export class SchemaMerger { alt.node.metadata.includesSchema = true } if (alt !== winner) { - if (winner.node === alt.node) { - same.push(alt) - } else if (ext === '.schema') { - // Check for same content which can happen when project and nuget from project are - // both being used. - const winnerSrc = await fs.readFile(winner.path, 'utf8') - const altSrc = await fs.readFile(alt.path, 'utf8') - if (winnerSrc !== altSrc) { + if (!winnerSrc) { + winnerSrc = await fs.readFile(winner.path, 'utf8') + } + const altSrc = await fs.readFile(alt.path, 'utf8') + // If content is identical, then don't treat as a duplicate. + // This is mainly about nuget packages which like to have multiple copies of files. + if (winnerSrc !== altSrc) { + if (winner.node === alt.node) { + same.push(alt) + } else if (ext === '.schema') { + const winnerSrc = await fs.readFile(winner.path, 'utf8') + const altSrc = await fs.readFile(alt.path, 'utf8') + if (winnerSrc !== altSrc) { + conflicts.push(alt) + } + } else if (ext !== '.uischema') { conflicts.push(alt) } - } else if (ext !== '.uischema') { - conflicts.push(alt) } } } diff --git a/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/Microsoft.AdaptiveDialog.schema b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/Microsoft.AdaptiveDialog.schema new file mode 100644 index 000000000..95fb1d9a2 --- /dev/null +++ b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/Microsoft.AdaptiveDialog.schema @@ -0,0 +1,65 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "$role": "implements(Microsoft.IDialog)", + "title": "Adaptive Dialog", + "description": "Flexible, data driven dialog that can adapt to the conversation.", + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Optional dialog ID." + }, + "autoEndDialog": { + "$ref": "schema:#/definitions/booleanExpression", + "title": "Auto end dialog", + "description": "If set to true the dialog will automatically end when there are no further actions. If set to false, remember to manually end the dialog using EndDialog action.", + "default": true + }, + "defaultResultProperty": { + "type": "string", + "title": "Default result property", + "description": "Value that will be passed back to the parent dialog.", + "default": "dialog.result" + }, + "recognizer": { + "$kind": "Microsoft.IRecognizer", + "title": "Recognizer", + "description": "Input recognizer that interprets user input into intent and entities." + }, + "generator": { + "$kind": "Microsoft.ILanguageGenerator", + "title": "Language Generator", + "description": "Language generator that generates bot responses." + }, + "selector": { + "$kind": "Microsoft.ITriggerSelector", + "title": "Selector", + "description": "Policy to determine which trigger is executed. Defaults to a 'best match' selector (optional)." + }, + "triggers": { + "type": "array", + "description": "List of triggers defined for this dialog.", + "title": "Triggers", + "items": { + "$kind": "Microsoft.ITrigger", + "title": "Event triggers", + "description": "Event triggers for handling events." + } + }, + "schema": { + "title": "Schema", + "description": "Schema to fill in.", + "anyOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema#" + }, + { + "type": "string", + "title": "Reference to JSON schema", + "description": "Reference to JSON schema .dialog file." + } + ] + } + } +} diff --git a/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/Microsoft.AdaptiveDialog.uischema b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/Microsoft.AdaptiveDialog.uischema new file mode 100644 index 000000000..37553d142 --- /dev/null +++ b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/Microsoft.AdaptiveDialog.uischema @@ -0,0 +1,24 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/ui/v1.0/ui.schema", + "form": { + "label": "Adaptive dialog", + "description": "This configures a data driven dialog via a collection of events and actions.", + "helpLink": "https://aka.ms/bf-composer-docs-dialog", + "order": [ + "recognizer", + "*" + ], + "hidden": [ + "triggers", + "generator", + "selector", + "schema" + ], + "properties": { + "recognizer": { + "label": "Language Understanding", + "description": "To understand what the user says, your dialog needs a \"Recognizer\"; that includes example words and sentences that users may use." + } + } + } +} diff --git a/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.jpg b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.jpg new file mode 100644 index 000000000..ebc43fd72 --- /dev/null +++ b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.jpg @@ -0,0 +1 @@ +picture \ No newline at end of file diff --git a/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.lg b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.lg new file mode 100644 index 000000000..d2ce1d909 --- /dev/null +++ b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.lg @@ -0,0 +1,2 @@ +# template +- template \ No newline at end of file diff --git a/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.lu b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.lu new file mode 100644 index 000000000..e7b263a4b --- /dev/null +++ b/packages/dialog/test/commands/dialog/nuget/nuget3/1.0.0/duplicated/nuget3.lu @@ -0,0 +1,2 @@ +# intent +- intent \ No newline at end of file diff --git a/packages/dialog/test/commands/dialog/projects/project2/multiple.dialog b/packages/dialog/test/commands/dialog/projects/project2/multiple.dialog index e69de29bb..9e26dfeeb 100644 --- a/packages/dialog/test/commands/dialog/projects/project2/multiple.dialog +++ b/packages/dialog/test/commands/dialog/projects/project2/multiple.dialog @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/lu/package.json b/packages/lu/package.json index 074dae08b..a6259fc09 100644 --- a/packages/lu/package.json +++ b/packages/lu/package.json @@ -33,10 +33,10 @@ ] }, "dependencies": { - "@azure/cognitiveservices-luis-authoring": "4.0.0-preview.1", - "@azure/ms-rest-azure-js": "2.0.1", "@types/node-fetch": "~2.5.5", "antlr4": "~4.8.0", + "axios": "~0.21.1", + "axios-https-proxy": "^0.1.1", "chalk": "2.4.1", "console-stream": "^0.1.1", "deep-equal": "^1.0.1", diff --git a/packages/lu/src/parser/lubuild/core.ts b/packages/lu/src/parser/lubuild/core.ts index a3f6fcc26..48bfcb3b3 100644 --- a/packages/lu/src/parser/lubuild/core.ts +++ b/packages/lu/src/parser/lubuild/core.ts @@ -3,20 +3,17 @@ * Licensed under the MIT License. */ -import {CognitiveServicesCredentials} from '@azure/ms-rest-azure-js' -import {LUISAuthoringClient} from '@azure/cognitiveservices-luis-authoring' -import fetch from 'node-fetch' +import httpRequest from './http-request' const delay = require('delay') const os = require('os') const Luis = require('./../luis/luis') const packageJSON = require('./../../../package') -const rateLimitErrorCode = 429 +const rateLimitErrorCode = '429' const absoluteUrlPattern = /^https?:\/\//i export class LuBuildCore { - private readonly client: any private readonly subscriptionKey: string private readonly endpoint: string private readonly retryCount: number @@ -26,7 +23,7 @@ export class LuBuildCore { constructor(subscriptionKey: string, endpoint: string, retryCount: number, retryDuration: number) { this.subscriptionKey = subscriptionKey - this.endpoint = endpoint + this.endpoint = `${endpoint}/luis/authoring/v3.0-preview` this.retryCount = retryCount this.retryDuration = retryDuration @@ -47,30 +44,24 @@ export class LuBuildCore { 'User-Agent': luisUserAgent, 'Ocp-Apim-Subscription-Key': this.subscriptionKey, } - - // new luis api client - const options = { - userAgent: luisUserAgent - } - - const creds = new CognitiveServicesCredentials(subscriptionKey) - this.client = new LUISAuthoringClient(creds, endpoint, options) } public async getApplicationList() { + const url = `${this.endpoint}/apps` + let apps let retryCount = this.retryCount + 1 let error while (retryCount > 0) { - if (error === undefined || error.statusCode === rateLimitErrorCode) { - try { - apps = await this.client.apps.list(undefined, undefined) - break - } catch (e) { - error = e - retryCount-- - if (retryCount > 0) await delay(this.retryDuration) - } + if (error === undefined || error.code === rateLimitErrorCode) { + + apps = await httpRequest.get(url, this.headers) + + if (apps.error === undefined) break + + error = apps.error + retryCount-- + if (retryCount > 0) await delay(this.retryDuration) } else { throw error } @@ -84,19 +75,20 @@ export class LuBuildCore { } public async getApplicationInfo(appId: string) { + const url = `${this.endpoint}/apps/${appId}` + let appInfo let retryCount = this.retryCount + 1 let error while (retryCount > 0) { - if (error === undefined || error.statusCode === rateLimitErrorCode) { - try { - appInfo = await this.client.apps.get(appId) - break - } catch (e) { - error = e - retryCount-- - if (retryCount > 0) await delay(this.retryDuration) - } + if (error === undefined || error.code === rateLimitErrorCode) { + appInfo = await httpRequest.get(url, this.headers) + + if (appInfo.error === undefined) break + + error = appInfo.error + retryCount-- + if (retryCount > 0) await delay(this.retryDuration) } else { throw error } @@ -110,18 +102,15 @@ export class LuBuildCore { } public async importApplication(currentApp: any): Promise { - // let response = await this.client.apps.importMethod(currentApp) - const name = `?appName=${currentApp.name}` - const url = this.endpoint + '/luis/authoring/v3.0-preview/apps/import' + name + const url = `${this.endpoint}/apps/import${name}` let messageData let retryCount = this.retryCount + 1 let error: any while (retryCount > 0) { - if (error === undefined || error.code === rateLimitErrorCode.toString()) { - let response = await fetch(url, {method: 'POST', headers: this.headers, body: JSON.stringify(currentApp)}) - messageData = await response.json() + if (error === undefined || error.code === rateLimitErrorCode) { + messageData = await httpRequest.post(url, JSON.stringify(currentApp), this.headers) if (messageData.error === undefined) break @@ -141,22 +130,20 @@ export class LuBuildCore { } public async exportApplication(appId: string, versionId: string) { - const url = this.endpoint + '/luis/authoring/v3.0-preview/apps/' + appId + '/versions/' + versionId + '/export?format=json' + const url = `${this.endpoint}/apps/${appId}/versions/${versionId}/export?format=json` let messageData let retryCount = this.retryCount + 1 let error while (retryCount > 0) { - if (error === undefined || error.statusCode === rateLimitErrorCode) { - try { - const response = await fetch(url, {method: 'GET', headers: this.headers}) - messageData = await response.json() - break - } catch (e) { - error = e - retryCount-- - if (retryCount > 0) await delay(this.retryDuration) - } + if (error === undefined || error.code === rateLimitErrorCode) { + messageData = await httpRequest.get(url, this.headers) + + if (messageData.error === undefined) break + + error = messageData.error + retryCount-- + if (retryCount > 0) await delay(this.retryDuration) } else { throw error } @@ -166,10 +153,6 @@ export class LuBuildCore { throw error } - if (messageData.error) { - throw new Error(messageData.error.message) - } - return messageData } @@ -224,19 +207,15 @@ export class LuBuildCore { } public async importNewVersion(appId: string, app: any, options: any) { - // await this.client.versions.importMethod(appId, app, options) - const versionId = `?versionId=${options.versionId}` - let url = this.endpoint + '/luis/authoring/v3.0-preview/apps/' + appId + '/versions/import' + versionId + let url = `${this.endpoint}/apps/${appId}/versions/import${versionId}` let messageData let retryCount = this.retryCount + 1 let error: any while (retryCount > 0) { - if (error === undefined || error.code === rateLimitErrorCode.toString()) { - let response = await fetch(url, {method: 'POST', headers: this.headers, body: JSON.stringify(app)}) - messageData = await response.json() - + if (error === undefined || error.code === rateLimitErrorCode) { + messageData = await httpRequest.post(url, JSON.stringify(app), this.headers) if (messageData.error === undefined) break error = messageData.error @@ -255,19 +234,19 @@ export class LuBuildCore { } public async listApplicationVersions(appId: string) { + let url = `${this.endpoint}/apps/${appId}/versions` + let appVersions let retryCount = this.retryCount + 1 let error while (retryCount > 0) { - if (error === undefined || error.statusCode === rateLimitErrorCode) { - try { - appVersions = await this.client.versions.list(appId) - break - } catch (e) { - error = e - retryCount-- - if (retryCount > 0) await delay(this.retryDuration) - } + if (error === undefined || error.code === rateLimitErrorCode) { + appVersions = await httpRequest.get(url, this.headers) + if (appVersions.error === undefined) break + + error = appVersions.error + retryCount-- + if (retryCount > 0) await delay(this.retryDuration) } else { throw error } @@ -281,18 +260,19 @@ export class LuBuildCore { } public async deleteVersion(appId: string, versionId: string) { + let url = `${this.endpoint}/apps/${appId}/versions/${versionId}` + + let messageData let retryCount = this.retryCount + 1 let error while (retryCount > 0) { - if (error === undefined || error.statusCode === rateLimitErrorCode) { - try { - await this.client.versions.deleteMethod(appId, versionId) - break - } catch (e) { - error = e - retryCount-- - if (retryCount > 0) await delay(this.retryDuration) - } + if (error === undefined || error.code === rateLimitErrorCode) { + messageData = await httpRequest.delete(url, this.headers) + if (messageData.error === undefined) break + + error = messageData.error + retryCount-- + if (retryCount > 0) await delay(this.retryDuration) } else { throw error } @@ -301,20 +281,21 @@ export class LuBuildCore { if (retryCount === 0) { throw error } + + return messageData } public async trainApplication(appId: string, versionId: string, trainMode: string) { let mode = trainMode || this.trainMode - let url = `${this.endpoint}/luis/authoring/v3.0-preview/apps/${appId}/versions/${versionId}/train` + let url = `${this.endpoint}/apps/${appId}/versions/${versionId}/train` url += mode ? `?mode=${mode}` : '' let messageData let retryCount = this.retryCount + 1 let error: any while (retryCount > 0) { - if (error === undefined || error.code === rateLimitErrorCode.toString()) { - let response = await fetch(url, {method: 'POST', headers: this.headers}) - messageData = await response.json() + if (error === undefined || error.code === rateLimitErrorCode) { + messageData = await httpRequest.post(url, '', this.headers) if (messageData.error === undefined) break @@ -334,19 +315,19 @@ export class LuBuildCore { } public async getTrainingStatus(appId: string, versionId: string) { + let url = `${this.endpoint}/apps/${appId}/versions/${versionId}/train` + let status let retryCount = this.retryCount + 1 let error while (retryCount > 0) { - if (error === undefined || error.statusCode === rateLimitErrorCode) { - try { - status = await this.client.train.getStatus(appId, versionId) - break - } catch (e) { - error = e - retryCount-- - if (retryCount > 0) await delay(this.retryDuration) - } + if (error === undefined || error.code === rateLimitErrorCode) { + status = await httpRequest.get(url, this.headers) + if (status.error === undefined) break + + error = status.error + retryCount-- + if (retryCount > 0) await delay(this.retryDuration) } else { throw error } @@ -360,19 +341,21 @@ export class LuBuildCore { } public async publishApplication(appId: string, versionId: string, isStaging: boolean, directVersionPublish: boolean) { - let url = this.endpoint + '/luis/authoring/v3.0-preview/apps/' + appId + '/publish' + let url = `${this.endpoint}/apps/${appId}/publish` let messageData let retryCount = this.retryCount + 1 let error: any while (retryCount > 0) { - if (error === undefined || error.code === rateLimitErrorCode.toString()) { - let response = await fetch(url, {method: 'POST', headers: this.headers, body: JSON.stringify({ - versionId, - isStaging, - directVersionPublish - })}) - messageData = await response.json() + if (error === undefined || error.code === rateLimitErrorCode) { + messageData = await httpRequest.post( + url, + JSON.stringify({ + versionId, + isStaging, + directVersionPublish + }), + this.headers) if (messageData.error === undefined) break diff --git a/packages/lu/src/parser/lubuild/http-request.ts b/packages/lu/src/parser/lubuild/http-request.ts new file mode 100644 index 000000000..f87ef9f05 --- /dev/null +++ b/packages/lu/src/parser/lubuild/http-request.ts @@ -0,0 +1,90 @@ +/*! + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +const axios = require('axios') +const axiosHttpsProxy = require('axios-https-proxy') +axios.interceptors.request.use(axiosHttpsProxy) + +export default { + async get( + url: string, + headers: any) { + const resp = await httpRequest( + { + method: 'GET', + url, + headers + }) + + return resp.data + }, + + async post( + url: string, + body: any, + headers: any) { + const resp = await httpRequest( + { + method: 'POST', + url, + data: body, + headers + }) + + return resp.data + }, + + async put( + url: string, + body: any, + headers: any) { + const resp = await httpRequest( + { + method: 'PUT', + url, + data: body, + headers + }) + + return isJSON(resp.data) ? resp.data : {code: 'Success'} + }, + + async delete( + url: string, + headers: any) { + const resp = await httpRequest( + { + method: 'DELETE', + url, + headers + }) + return isJSON(resp.data) ? resp.data : {code: 'Success'} + } +} + +const httpRequest = async function (config: any) { + try { + return await axios(config) + } catch (error) { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + return error.response + } else { + // Something happened in setting up the request that triggered an Error + return Error(error.message) + } + } +} + +/* tslint:disable:no-unused */ +const isJSON = function (jsonObject: any) { + try { + JSON.parse(jsonObject + '') + } catch (error) { + return false + } + return true +} diff --git a/packages/lu/src/parser/qnabuild/core.ts b/packages/lu/src/parser/qnabuild/core.ts index 0e0169080..3ec359bc0 100644 --- a/packages/lu/src/parser/qnabuild/core.ts +++ b/packages/lu/src/parser/qnabuild/core.ts @@ -21,111 +21,93 @@ export class QnaBuildCore { } public async getKBList() { - const response = await this.service.createRequest('/knowledgebases', 'GET') - const text = await response.text() - const kbList = JSON.parse(text) - if (kbList.error) { - throw new Error(kbList.error.message) + const kbList = await this.service.httpRequest('/knowledgebases', 'GET') + if (kbList?.error) { + throw kbList.error } return kbList } public async getKB(kbId: string) { - const response = await this.service.createRequest(`/knowledgebases/${kbId}`, 'GET') - const text = await response.text() - const kb = JSON.parse(text) - if (kb.error) { - throw new Error(kb.error.message) + const kb = await this.service.httpRequest(`/knowledgebases/${kbId}`, 'GET') + if (kb?.error) { + throw kb.error } return kb } public async importKB(kbPayload: any) { - const response = await this.service.createRequest('/knowledgebases/createasync', 'POST', kbPayload) - const text = await response.text() - const status = JSON.parse(text) - if (status.error) { - throw new Error(status.error.message) + const status = await this.service.httpRequest('/knowledgebases/createasync', 'POST', kbPayload) + if (status?.error) { + throw status.error } return status } public async getOperationStatus(operationId: string) { - const response = await this.service.createRequest(`/operations/${operationId}`, 'GET') - const text = await response.text() - const status = JSON.parse(text) - if (status.error) { - throw new Error(status.error.message) + const status = await this.service.httpRequest(`/operations/${operationId}`, 'GET') + if (status?.error) { + throw status.error } return status } public async exportKB(kbId: string, environment: string) { - const response = await this.service.createRequest(`/knowledgebases/${kbId}/${environment}/qna`, 'GET') - const text = await response.text() - const kb = JSON.parse(text) - if (kb.error) { - throw new Error(kb.error.message) + const kb = await this.service.httpRequest(`/knowledgebases/${kbId}/${environment}/qna`, 'GET') + if (kb?.error) { + throw kb.error } return kb } public async updateKB(kbId: string, replaceKb: any) { - const response = await this.service.createRequest(`/knowledgebases/${kbId}`, 'PATCH', replaceKb) - const text = await response.text() - const status = JSON.parse(text) - if (status.error) { - throw new Error(status.error.message) + const status = await this.service.httpRequest(`/knowledgebases/${kbId}`, 'PATCH', replaceKb) + if (status?.error) { + throw status.error } return status } public async replaceKB(kbId: string, replaceKb: any) { - const response = await this.service.createRequest(`/knowledgebases/${kbId}`, 'PUT', replaceKb) - const text = await response.text() - if (text) { - throw new Error(JSON.parse(text).error.message) + const text = await this.service.httpRequest(`/knowledgebases/${kbId}`, 'PUT', replaceKb) + if (text?.error) { + throw text.error } } public async publishKB(kbId: string) { - const response = await this.service.createRequest(`/knowledgebases/${kbId}`, 'POST') - const text = await response.text() - if (text) { - throw new Error(JSON.parse(text).error.message) + const text = await this.service.httpRequest(`/knowledgebases/${kbId}`, 'POST') + if (text?.error) { + throw text.error } } public async replaceAlt(altJson: any) { - const response = await this.service.createRequest('/alterations', 'PUT', altJson) - const text = await response.text() - if (text) { - throw new Error(JSON.parse(text).error.message) + const text = await this.service.httpRequest('/alterations', 'PUT', altJson) + if (text?.error) { + throw text.error } } public async getEndpointKeys() { - const response = await this.service.createRequest('/endpointkeys', 'GET') - const text = await response.text() - const endpointKeys = JSON.parse(text) - if (endpointKeys.error) { - throw new Error(endpointKeys.error.message) + const endpointKeys = await this.service.httpRequest('/endpointkeys', 'GET') + if (endpointKeys?.error) { + throw endpointKeys.error } return endpointKeys } public async deleteKB(kbId: string) { - const response = await this.service.createRequest(`/knowledgebases/${kbId}`, 'DELETE') - const text = await response.text() - if (text) { - throw new Error(JSON.parse(text).error.message) + const text = await this.service.httpRequest(`/knowledgebases/${kbId}`, 'DELETE') + if (text?.error) { + throw text.error } } diff --git a/packages/lu/src/parser/qnabuild/serviceBase.js b/packages/lu/src/parser/qnabuild/serviceBase.js index 995c667c0..cd0f3abb2 100644 --- a/packages/lu/src/parser/qnabuild/serviceBase.js +++ b/packages/lu/src/parser/qnabuild/serviceBase.js @@ -2,28 +2,12 @@ * Copyright(c) Microsoft Corporation.All rights reserved. * Licensed under the MIT License. */ + +const axios = require('axios') +const axiosHttpsProxy = require('axios-https-proxy') const os = require('os') const packageJSON = require('./../../../package') - -const fetch = require('node-fetch') - -global.fetch = function (...args) { - // No Proxy - if (!process.env.HTTPS_PROXY) { - return fetch(...args) - } - const [urlOrRequest, requestInit = {}, ...rest] = args - // URL is first param attach the proxy - // to the RequestInit - const HttpsProxyAgent = require('https-proxy-agent') - const agent = new HttpsProxyAgent(process.env.HTTPS_PROXY) - if (typeof urlOrRequest === 'string') { - requestInit.agent = agent - } else { - urlOrRequest.agent = agent - } - return fetch(urlOrRequest, requestInit, ...rest) -} +axios.interceptors.request.use(axiosHttpsProxy) /** * Base class for all services @@ -48,7 +32,7 @@ class ServiceBase { * @param {any} data The request data * @returns {Promise} The promise representing the request */ - createRequest(relativeEndpoint, method, data) { + async httpRequest(relativeEndpoint, method, data) { let URL = this.rootEndpoint + relativeEndpoint let body if (typeof data === 'string') { @@ -60,7 +44,24 @@ class ServiceBase { body = JSON.stringify(data) } - return fetch(URL, {headers: this.headers, method, body}) + let res + try { + let response = await axios({method, url: URL, headers: this.headers, data: body}) + res = response.data + } catch (error) { + if (error.response) { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + res = error.response.data + } else { + // Something happened in setting up the request that triggered an Error + return Error(error.message) + } + } + } + + return res } commonHeaders(subscriptionKey) { diff --git a/packages/lu/test/parser/lubuild/lubuild.test.js b/packages/lu/test/parser/lubuild/lubuild.test.js index e06ac9057..ffa4e2608 100644 --- a/packages/lu/test/parser/lubuild/lubuild.test.js +++ b/packages/lu/test/parser/lubuild/lubuild.test.js @@ -72,6 +72,7 @@ describe('builder: getActiveVersionIds function return version id sucessfully wi .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -80,6 +81,7 @@ describe('builder: getActiveVersionIds function return version id sucessfully wi .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -88,6 +90,7 @@ describe('builder: getActiveVersionIds function return version id sucessfully wi .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -134,6 +137,7 @@ describe('builder: getActiveVersionIds function return version id failed with ma .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -142,6 +146,7 @@ describe('builder: getActiveVersionIds function return version id failed with ma .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -150,6 +155,7 @@ describe('builder: getActiveVersionIds function return version id failed with ma .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -158,6 +164,7 @@ describe('builder: getActiveVersionIds function return version id failed with ma .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -199,6 +206,7 @@ describe('builder: getActiveVersionIds function return version id failed for non .get(uri => uri.includes('apps')) .reply(429, { error: { + code: '429', message: 'Rate limit is exceeded' } }) @@ -207,6 +215,7 @@ describe('builder: getActiveVersionIds function return version id failed for non .get(uri => uri.includes('apps')) .reply(401, { error: { + code: '401', message: 'You do not have access' } })