Skip to content

Commit

Permalink
feat(Context): add context.setAsHandled() and context.setAsNotHandled()
Browse files Browse the repository at this point in the history
  • Loading branch information
chentsulin committed Jan 13, 2020
1 parent c1dc152 commit c51d52a
Show file tree
Hide file tree
Showing 23 changed files with 176 additions and 875 deletions.
94 changes: 94 additions & 0 deletions packages/bottender-dialogflow/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ it('should resolve corresponding action if intent match name', async () => {

expect(context.sendText).toBeCalledWith('Hello!');
expect(context.intent).toEqual('greeting');
expect(context.isHandled).toEqual(true);

expect(sessionClient.sessionPath).toBeCalledWith('PROJECT_ID', 'test:1');
expect(sessionClient.detectIntent).toBeCalledWith({
Expand Down Expand Up @@ -222,6 +223,7 @@ it('should resolve corresponding action if intent match name', async () => {

expect(context.sendText).toBeCalledWith('Hello!');
expect(context.intent).toEqual('greeting');
expect(context.isHandled).toEqual(true);
});

it('should go next if intent does not match any name', async () => {
Expand Down Expand Up @@ -331,6 +333,98 @@ it('should go next if intent does not match any name', async () => {

expect(context.sendText).toBeCalledWith('Sorry, I don’t know what you say.');
expect(context.intent).toEqual('order');
expect(context.isHandled).toEqual(true);
});

it('should go next if no intent', async () => {
const { context } = setup();

const sessionPath = {};
let sessionClient;

dialogflowSdk.SessionsClient.mockImplementationOnce(() => {
sessionClient = {
sessionPath: jest.fn().mockReturnValue(sessionPath),
detectIntent: jest.fn().mockResolvedValue([
{
responseId: 'cb8e7a38-910a-4386-b312-eac8660d66f7-b4ef8d5f',
queryResult: {
fulfillmentMessages: [
{
platform: 'PLATFORM_UNSPECIFIED',
text: {
text: ['?'],
},
message: 'text',
},
],
outputContexts: [
{
name:
'projects/PROJECT_ID/agent/sessions/console:1/contexts/__system_counters__',
lifespanCount: 1,
parameters: {
fields: {
'no-match': {
numberValue: 5,
kind: 'numberValue',
},
'no-input': {
numberValue: 0,
kind: 'numberValue',
},
},
},
},
],
queryText: 'hi',
speechRecognitionConfidence: 0,
action: 'input.unknown',
parameters: {
fields: {},
},
allRequiredParamsPresent: true,
fulfillmentText: '?',
webhookSource: '',
webhookPayload: null,
intent: null,
intentDetectionConfidence: 1,
diagnosticInfo: null,
languageCode: 'zh-tw',
sentimentAnalysisResult: null,
},
webhookStatus: null,
outputAudio: {
type: 'Buffer',
data: [],
},
outputAudioConfig: null,
},
null,
null,
]),
};

return sessionClient;
});

const app = run(
chain([
dialogflow({
projectId: 'PROJECT_ID',
actions: {
greeting: SayHello,
},
}),
Unknown,
])
);

await app(context);

expect(context.sendText).toBeCalledWith('Sorry, I don’t know what you say.');
expect(context.intent).toBeNull();
expect(context.isHandled).toEqual(false);
});

it('should support parameters of dialogflow', async () => {
Expand Down
11 changes: 7 additions & 4 deletions packages/bottender-dialogflow/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ module.exports = function dialogflow({

if (intent) {
context.setIntent(intent.displayName);
}
context.setAsHandled();

const Action = actions[intent.name] || actions[intent.displayName];
if (Action) {
return withProps(Action as any, { intent, parameters });
const Action = actions[intent.name] || actions[intent.displayName];
if (Action) {
return withProps(Action as any, { intent, parameters });
}
} else {
context.setAsNotHandled();
}

return next;
Expand Down
2 changes: 2 additions & 0 deletions packages/bottender-luis/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ it('should resolve corresponding action if intent score > scoreThreshold', async

expect(context.sendText).toBeCalledWith('Hello!');
expect(context.intent).toEqual('greeting');
expect(context.isHandled).toEqual(true);

expect(requestHeaders['ocp-apim-subscription-key']).toEqual('APP_KEY');
expect(requestQuery).toEqual({
Expand Down Expand Up @@ -219,6 +220,7 @@ it('should go next if intent confidence < confidenceThreshold', async () => {

expect(context.sendText).toBeCalledWith('Sorry, I don’t know what you say.');
expect(context.intent).toBeNull();
expect(context.isHandled).toEqual(false);
});

it('should support parameters of luis', async () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/bottender-luis/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ module.exports = function luis({

if (topScoringIntent && topScoringIntent.score > scoreThreshold) {
context.setIntent(topScoringIntent.intent);
context.setAsHandled();

const Action = actions[topScoringIntent.intent];

Expand All @@ -85,6 +86,8 @@ module.exports = function luis({
sentimentAnalysis,
});
}
} else {
context.setAsNotHandled();
}

return next;
Expand Down
3 changes: 3 additions & 0 deletions packages/bottender-qna-maker/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ it('should reply with answer if id of answer is not -1', async () => {
'There is no direct integration of LUIS with QnA Maker. But, in your bot code, you can use LUIS and QnA Maker together. [View a sample bot](https://github.com/Microsoft/BotBuilder-CognitiveServices/tree/master/Node/samples/QnAMaker/QnAWithLUIS)'
);
expect(context.intent).toEqual('qna_20');
expect(context.isHandled).toEqual(true);

expect(requestBody).toEqual({
question: 'text',
Expand Down Expand Up @@ -138,6 +139,7 @@ it('should go next if id of answer is -1', async () => {

expect(context.sendText).toBeCalledWith('Sorry, I don’t know what you say.');
expect(context.intent).toBeNull();
expect(context.isHandled).toEqual(false);
});

it('should reply unknown answer from qna maker if id of answer is -1 and no next in chain', async () => {
Expand Down Expand Up @@ -172,6 +174,7 @@ it('should reply unknown answer from qna maker if id of answer is -1 and no next

expect(context.sendText).toBeCalledWith('No good match found in KB.');
expect(context.intent).toBeNull();
expect(context.isHandled).toEqual(false);
});

it('should go next when receiving a non-text event', async () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/bottender-qna-maker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ module.exports = function qnaMaker({
const topAnswer = data.answers[0];

if (!topAnswer || (next && topAnswer.id === -1)) {
context.setAsNotHandled();
return next;
}

return async function TopAnswer() {
if (topAnswer.id !== -1) {
context.setIntent(`qna_${topAnswer.id}`);
context.setAsHandled();
} else {
context.setAsNotHandled();
}

await context.sendText(topAnswer.answer);
Expand Down
2 changes: 2 additions & 0 deletions packages/bottender-rasa/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ it('should resolve corresponding action if intent confidence > confidenceThresho

expect(context.sendText).toBeCalledWith('Hello!');
expect(context.intent).toEqual('greeting');
expect(context.isHandled).toEqual(true);

expect(requestBody).toEqual({
text: 'text',
Expand Down Expand Up @@ -156,6 +157,7 @@ it('should go next if intent confidence < confidenceThreshold', async () => {

expect(context.sendText).toBeCalledWith('Sorry, I don’t know what you say.');
expect(context.intent).toBeNull();
expect(context.isHandled).toEqual(false);
});

it('should support JWT', async () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/bottender-rasa/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ module.exports = function rasa({

if (intent && intent.confidence > confidenceThreshold) {
context.setIntent(intent.name);
context.setAsHandled();

const Action = actions[intent.name];
if (Action) {
return withProps(Action as any, { intent, entities });
}
} else {
context.setAsNotHandled();
}

return next;
Expand Down
2 changes: 0 additions & 2 deletions packages/bottender/src/console/ConsoleContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export default class ConsoleContext extends Context<
*
*/
async sendText(text: string, ...args: any[]): Promise<void> {
this._isHandled = true;
if (args.length > 0 && this._fallbackMethods) {
this._client.sendText(
`${text}\nwith other args:\n${JSON.stringify(args, null, 2)}`
Expand All @@ -102,7 +101,6 @@ export default class ConsoleContext extends Context<
}

async _methodMissing(method: string, args: any[]): Promise<void> {
this._isHandled = true;
this._client.sendText(
`${method} with args:\n${JSON.stringify(args, null, 2)}`
);
Expand Down
16 changes: 0 additions & 16 deletions packages/bottender/src/console/__tests__/ConsoleContext.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,6 @@ describe('#sendText', () => {
expect(client.sendText).toBeCalledWith('hello');
});

it('should mark context as handled', async () => {
const { context } = setup();

await context.sendText('hello');

expect(context.isHandled).toBe(true);
});

it('should support fallbackMethods with other args', async () => {
const { context, client } = setup({ fallbackMethods: true });

Expand Down Expand Up @@ -131,14 +123,6 @@ describe('method missing', () => {
);
});

it('should mark context as handled', async () => {
const { context } = setup({ fallbackMethods: true });

await context.sendABC('hello');

expect(context.isHandled).toBe(true);
});

it('should not proxy blacklisted methods', async () => {
const { context } = setup({ fallbackMethods: true });

Expand Down
20 changes: 18 additions & 2 deletions packages/bottender/src/context/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default abstract class Context<C extends Client, E extends Event> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
abstract sendText(text: string, options?: Record<string, any>): any;

_isHandled = false;
_isHandled: boolean | null = null;

_isSessionWritten = false;

Expand Down Expand Up @@ -117,7 +117,7 @@ export default abstract class Context<C extends Client, E extends Event> {
return this._session;
}

get isHandled(): boolean {
get isHandled(): boolean | null {
return this._isHandled;
}

Expand Down Expand Up @@ -205,6 +205,22 @@ export default abstract class Context<C extends Client, E extends Event> {
this._intent = intent;
}

/**
* Set the conversation context as handled or not handled by boolean.
*
*/
setAsHandled(handled = true) {
this._isHandled = handled;
}

/**
* Set the conversation context as not handled.
*
*/
setAsNotHandled() {
this.setAsHandled(false);
}

emitError(err: Error): void {
if (this._emitter) {
this._emitter.emit('error', err, this);
Expand Down
40 changes: 40 additions & 0 deletions packages/bottender/src/context/__tests__/Context.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,43 @@ describe('intent', () => {
expect(context.intent).toEqual('hello-world');
});
});

describe('handled', () => {
it('should default to null', () => {
const context = new TestContext({ client: {}, event: {} });

expect(context.isHandled).toBeNull();
});

it('should be true after calling context.setAsHandled()', () => {
const context = new TestContext({ client: {}, event: {} });

context.setAsHandled();

expect(context.isHandled).toEqual(true);
});

it('should be true after calling context.setAsHandled(true)', () => {
const context = new TestContext({ client: {}, event: {} });

context.setAsHandled(true);

expect(context.isHandled).toEqual(true);
});

it('should be false after calling context.setAsHandled(false)', () => {
const context = new TestContext({ client: {}, event: {} });

context.setAsHandled(false);

expect(context.isHandled).toEqual(false);
});

it('should be false after calling context.setAsNotHandled()', () => {
const context = new TestContext({ client: {}, event: {} });

context.setAsNotHandled();

expect(context.isHandled).toEqual(false);
});
});
Loading

0 comments on commit c51d52a

Please sign in to comment.