Skip to content

Commit a21e186

Browse files
authored
Use v5 API host in QnAMakerDialog or construct v4 API host (#2004)
* add backwards-compat for QnAMakerDialog.getQnAClient, add tests * add bad scenario tests to getQnAClient() * add http getQnAClient() test * add happy test for hostName without azurewebsites.net * refactor QnAMakerDialog to use v5 hostName or construct v4 host URL * refactor v4 API host support in QnAMakerDialog
1 parent 17f06de commit a21e186

File tree

4 files changed

+134
-15
lines changed

4 files changed

+134
-15
lines changed

libraries/botbuilder-ai/src/qnaMaker.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
* Copyright (c) Microsoft Corporation. All rights reserved.
66
* Licensed under the MIT License.
77
*/
8-
import { Activity, TurnContext, BotTelemetryClient, NullTelemetryClient } from 'botbuilder-core';
8+
import { TurnContext, BotTelemetryClient, NullTelemetryClient } from 'botbuilder-core';
99

10-
import { constants } from 'http2';
1110
import { QnATelemetryConstants } from './qnaTelemetryConstants';
1211
import { QnAMakerEndpoint } from './qnamaker-interfaces/qnamakerEndpoint';
1312
import { QnAMakerMetadata } from './qnamaker-interfaces/qnamakerMetadata';

libraries/botbuilder-ai/src/qnaMakerDialog.ts

+58-5
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
* Licensed under the MIT License.
77
*/
88
import { Activity, ActivityTypes } from 'botbuilder-core';
9-
import { WaterfallDialog, Dialog, DialogTurnResult, DialogContext, WaterfallStepContext, DialogTurnStatus, DialogReason } from 'botbuilder-dialogs';
9+
import { WaterfallDialog, Dialog, DialogTurnResult, DialogContext, WaterfallStepContext, DialogReason } from 'botbuilder-dialogs';
1010
import { QnAMakerOptions } from './qnamaker-interfaces/qnamakerOptions';
1111
import { RankerTypes } from './qnamaker-interfaces/rankerTypes';
1212
import { QnAMaker, QnAMakerResult } from './';
1313
import { FeedbackRecord, FeedbackRecords, QnAMakerMetadata } from './qnamaker-interfaces';
1414
import { QnACardBuilder } from './qnaCardBuilder';
1515

16+
const V4_API_REGEX = /^https:\/\/.*\.azurewebsites\.net\/qnamaker\/?/i;
17+
1618
/**
1719
* QnAMakerDialog response options.
1820
*/
@@ -91,12 +93,23 @@ export class QnAMakerDialog extends WaterfallDialog {
9193
* @param strictFilters (Optional) QnAMakerMetadata collection used to filter / boost queries to the knowledgebase.
9294
* @param dialogId (Optional) Id of the created dialog. Default is 'QnAMakerDialog'.
9395
*/
94-
constructor(knowledgeBaseId: string, endpointKey: string, hostName: string, noAnswer?: Activity, threshold: number = 0.3, activeLearningCardTitle: string = 'Did you mean:', cardNoMatchText: string = 'None of the above.', top: number = 3, cardNoMatchResponse?: Activity, strictFilters?: QnAMakerMetadata[], dialogId: string = 'QnAMakerDialog') {
96+
public constructor(knowledgeBaseId: string, endpointKey: string, hostName: string, noAnswer?: Activity, threshold: number = 0.3, activeLearningCardTitle: string = 'Did you mean:', cardNoMatchText: string = 'None of the above.', top: number = 3, cardNoMatchResponse?: Activity, strictFilters?: QnAMakerMetadata[], dialogId: string = 'QnAMakerDialog') {
9597
super(dialogId);
98+
if (!knowledgeBaseId) {
99+
throw new TypeError('QnAMakerDialog: missing knowledgeBaseId parameter');
100+
}
101+
102+
if (!endpointKey) {
103+
throw new TypeError('QnAMakerDialog: missing endpointKey parameter');
104+
}
105+
106+
if (!hostName) {
107+
throw new TypeError('QnAMakerDialog: missing hostName parameter');
108+
}
96109

97110
this.knowledgeBaseId = knowledgeBaseId;
98-
this.hostName = hostName;
99111
this.endpointKey = endpointKey;
112+
this.hostName = hostName;
100113
this.threshold = threshold;
101114
this.top = top;
102115
this.activeLearningCardTitle = activeLearningCardTitle;
@@ -352,8 +365,48 @@ export class QnAMakerDialog extends WaterfallDialog {
352365
const endpoint = {
353366
knowledgeBaseId: this.knowledgeBaseId,
354367
endpointKey: this.endpointKey,
355-
host: this.hostName
368+
host: this.getHost()
356369
};
357370
return new QnAMaker(endpoint);
358371
}
359-
}
372+
373+
/**
374+
* Gets unmodified v5 API hostName or constructs v4 API hostName
375+
* @remarks
376+
* Example of a complete v5 API endpoint: "https://qnamaker-acom.azure.com/qnamaker/v5.0"
377+
* Template literal to construct v4 API endpoint: `https://${ this.hostName }.azurewebsites.net/qnamaker`
378+
*/
379+
private getHost(): string {
380+
let host: string = this.hostName;
381+
// If hostName includes 'qnamaker/v5', return the v5 API hostName.
382+
if (host.includes('qnamaker/v5')) {
383+
return host;
384+
}
385+
386+
// V4 API logic
387+
// If the hostname contains all the necessary information, return it
388+
if (/^https:\/\/.*\.azurewebsites\.net\/qnamaker\/?/i.test(host)) {
389+
return host;
390+
}
391+
392+
// Otherwise add required components
393+
if (!(/https?:\/\//i.test(host))) {
394+
host = 'https://' + host;
395+
}
396+
397+
// Web App Bots provisioned through the QnAMaker portal have "xxx.azurewebsites.net" in their
398+
// environment variables
399+
if (host.endsWith('.azurewebsites.net')) {
400+
// Add the remaining required path
401+
return host + '/qnamaker';
402+
}
403+
404+
// If this.hostName is just the azurewebsite subdomain, finish the remaining V4 API behavior shipped in 4.8.0
405+
// e.g. `https://${ this.hostName }.azurewebsites.net/qnamaker`
406+
if (!host.endsWith('.azurewebsites.net/qnamaker')) {
407+
host = host + '.azurewebsites.net/qnamaker';
408+
}
409+
410+
return host;
411+
}
412+
}

libraries/botbuilder-ai/src/qnamaker-interfaces/qnamakerEndpoint.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface QnAMakerEndpoint {
2222
endpointKey: string;
2323

2424
/**
25-
* The host path. For example: `https://westus.api.cognitive.microsoft.com/qnamaker/v2.0`
25+
* The host path. For example: `https://testqnamaker.azurewebsites.net/qnamaker`
2626
*/
2727
host: string;
2828
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,84 @@
1-
const { QnAMakerDialog } = require('../lib');
1+
const { QnAMakerDialog, QnAMaker } = require('../lib');
22
const { DialogSet } = require('botbuilder-dialogs');
3-
const assert = require('assert');
4-
3+
const { ok, strictEqual } = require('assert');
54

5+
const KB_ID = 'kbId';
6+
const ENDPOINT_KEY = 'endpointKey';
67

78
describe('QnAMakerDialog', function() {
9+
this.timeout(3000);
10+
11+
it('should successfully construct', () => {
12+
new QnAMakerDialog('kbId', 'endpointKey', 'https://myqnainstance.azurewebsites.net/qnamaker');
13+
});
14+
15+
it('should fail to construct with missing params', () => {
16+
try {
17+
new QnAMakerDialog(undefined, 'endpointKey', 'https://myqnainstance.azurewebsites.net/qnamaker');
18+
} catch (e) {
19+
strictEqual(e.message, 'QnAMakerDialog: missing knowledgeBaseId parameter');
20+
}
821

9-
it('should add instance to a dialog set', async function(done) {
22+
try {
23+
new QnAMakerDialog('kbId', undefined, 'https://myqnainstance.azurewebsites.net/qnamaker');
24+
} catch (e) {
25+
strictEqual(e.message, 'QnAMakerDialog: missing endpointKey parameter');
26+
}
27+
28+
try {
29+
new QnAMakerDialog('kbId', 'endpointKey', undefined);
30+
} catch (e) {
31+
strictEqual(e.message, 'QnAMakerDialog: missing hostName parameter');
32+
}
33+
});
34+
35+
it('should add instance to a dialog set', () => {
1036
const dialogs = new DialogSet();
11-
const qna = new QnAMakerDialog();
37+
const qna = new QnAMakerDialog('kbId', 'endpointKey', 'https://myqnainstance.azurewebsites.net/qnamaker');
1238

1339
dialogs.add(qna);
14-
done();
40+
});
1541

42+
describe('getQnAClient()', () => {
43+
it('should return unmodified v5 hostName value', () => {
44+
const V5_HOSTNAME = 'https://qnamaker-acom.azure.com/qnamaker/v5.0';
45+
46+
// Create QnAMakerDialog
47+
const qna = new QnAMakerDialog(KB_ID, ENDPOINT_KEY, V5_HOSTNAME);
48+
const client = qna.getQnAClient();
49+
50+
ok(client instanceof QnAMaker);
51+
strictEqual(client.endpoint.knowledgeBaseId, KB_ID);
52+
strictEqual(client.endpoint.endpointKey, ENDPOINT_KEY);
53+
strictEqual(client.endpoint.host, V5_HOSTNAME);
54+
});
55+
56+
it('should construct v4 API endpoint', () => {
57+
const INCOMPLETE_HOSTNAME = 'myqnainstance';
58+
const HOSTNAME = 'https://myqnainstance.azurewebsites.net/qnamaker';
59+
60+
// Create QnAMakerDialog with incomplete hostname
61+
const qnaDialog = new QnAMakerDialog(KB_ID, ENDPOINT_KEY, INCOMPLETE_HOSTNAME);
62+
const fixedClient = qnaDialog.getQnAClient();
63+
64+
ok(fixedClient instanceof QnAMaker);
65+
strictEqual(fixedClient.endpoint.knowledgeBaseId, KB_ID);
66+
strictEqual(fixedClient.endpoint.endpointKey, ENDPOINT_KEY);
67+
strictEqual(fixedClient.endpoint.host, HOSTNAME);
68+
});
69+
70+
it('should construct BAD v4 hostnames', () => {
71+
const createHostName = (hostName) => `https://${ hostName }.azurewebsites.net/qnamaker`;
72+
const NOT_V5_HOSTNAME = 'myqnainstance.net/qnamaker';
73+
74+
// Missing authority
75+
const noAuthority = new QnAMakerDialog(KB_ID, ENDPOINT_KEY, NOT_V5_HOSTNAME);
76+
const noAuthorityClient = noAuthority.getQnAClient();
77+
78+
ok(noAuthorityClient instanceof QnAMaker);
79+
strictEqual(noAuthorityClient.endpoint.knowledgeBaseId, KB_ID);
80+
strictEqual(noAuthorityClient.endpoint.endpointKey, ENDPOINT_KEY);
81+
strictEqual(noAuthorityClient.endpoint.host, createHostName(NOT_V5_HOSTNAME));
82+
});
1683
});
17-
});
84+
});

0 commit comments

Comments
 (0)