From 98037ca4f4a38ecb7b00cb77e103f95a0ae7e7ff Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 16 Jun 2021 11:30:10 -0700 Subject: [PATCH 01/13] add support for Teams meeting start/end events --- .../botbuilder/src/teamsActivityHandler.ts | 80 ++++++++++ .../tests/teamsActivityHandler.test.js | 143 +++++++++++++++++- .../botframework-schema/src/teams/index.ts | 58 +++++-- 3 files changed, 269 insertions(+), 12 deletions(-) diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index 6c50bd1ea0..db9fcde1b3 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -11,8 +11,11 @@ import { AdaptiveCardInvokeResponse, AppBasedLinkQuery, ChannelInfo, + Channels, FileConsentCardResponse, InvokeResponse, + MeetingStartEventDetails, + MeetingEndEventDetails, MessagingExtensionAction, MessagingExtensionActionResponse, MessagingExtensionQuery, @@ -924,4 +927,81 @@ export class TeamsActivityHandler extends ActivityHandler { await handler(teamsChannelData.team, context, next); }); } + + /** + * Runs the _event_ sub-type handlers, as appropriate, and then continues the event emission process. + * + * @param context The context object for the current turn. + * @returns A promise that represents the work queued. + * + * @remarks + * Overwrite this method to support channel-specific behavior across multiple channels or to add + * custom event sub-type events. + */ + protected async dispatchEventActivity(context: TurnContext): Promise { + await this.handle(context, 'Event', async () => { + if (context.activity.channelId == Channels.Msteams) { + switch (context.activity.name) { + case 'application/vnd.microsoft.meetingStart': + return this.onTeamsMeetingStart(context); + case 'application/vnd.microsoft.meetingEnd': + return this.onTeamsMeetingEnd(context); + } + } + + return super.dispatchEventActivity(context); + }); + } + + /** + * Invoked when a Meeting Started event activity is received from the connector. + * Override this in a derived class to provide logic for when a meeting is started. + * + * @param context The context for this turn. + * @returns A promise that represents the work queued. + */ + protected async onTeamsMeetingStart(context: TurnContext): Promise { + await this.handle(context, 'TeamsMeetingStart', this.defaultNextEvent(context)); + } + + /** + * Invoked when a Meeting End event activity is received from the connector. + * Override this in a derived class to provide logic for when a meeting is ended. + * + * @param context The context for this turn. + * @returns A promise that represents the work queued. + */ + protected async onTeamsMeetingEnd(context: TurnContext): Promise { + await this.handle(context, 'TeamsMeetingEnd', this.defaultNextEvent(context)); + } + + /** + * Registers a handler for when a Teams meeting starts. + * + * @param handler A callback that handles Meeting Start events. + * @returns A promise that represents the work queued. + */ + public onTeamsMeetingStartEvent( + handler: (meeting: MeetingStartEventDetails, context: TurnContext, next: () => Promise) => Promise + ): this { + return this.on('TeamsMeetingStart', async (context, next) => { + const meeting = context.activity.value as MeetingStartEventDetails; + await handler(meeting, context, next); + }); + } + + /** + * Registers a handler for when a Teams meeting ends. + * + * @param handler A callback that handles Meeting End events. + * @returns A promise that represents the work queued. + */ + public onTeamsMeetingEndEvent( + handler: (meeting: MeetingEndEventDetails, context: TurnContext, next: () => Promise) => Promise + ): this { + return this.on('TeamsMeetingEnd', async (context, next) => { + const meeting = context.activity.value as MeetingEndEventDetails; + await handler(meeting, context, next); + }); + } } diff --git a/libraries/botbuilder/tests/teamsActivityHandler.test.js b/libraries/botbuilder/tests/teamsActivityHandler.test.js index 4b52a0af17..9433f28aa3 100644 --- a/libraries/botbuilder/tests/teamsActivityHandler.test.js +++ b/libraries/botbuilder/tests/teamsActivityHandler.test.js @@ -1,6 +1,6 @@ const assert = require('assert'); const { TeamsActivityHandler, TeamsInfo } = require('../'); -const { ActivityTypes, TestAdapter } = require('botbuilder-core'); +const { ActivityTypes, TestAdapter, Channels } = require('botbuilder-core'); function createInvokeActivity(name, value = {}, channelData = {}) { const activity = { @@ -12,7 +12,7 @@ function createInvokeActivity(name, value = {}, channelData = {}) { return activity; } -describe('TeamsActivityHandler', () => { +describe('TeamsActivityHandler', function () { describe('onTurnActivity()', () => { it('should not override the InvokeResponse on the context.turnState if it is set', async function () { class InvokeHandler extends TeamsActivityHandler { @@ -2158,4 +2158,143 @@ describe('TeamsActivityHandler', () => { .startTest(); }); }); + + describe('onEventActivity()', function () { + const expectedMeeting = { id: 'meetingId' }; + + function createMeetingEventActivity(start = true) { + const activity = { + channelId: Channels.Msteams, + type: 'event', + value: expectedMeeting, + }; + + if (start) { + activity.name = 'application/vnd.microsoft.meetingStart'; + activity.value.startTime = '2021-06-05T00:01:02.0Z'; + } else { + activity.name = 'application/vnd.microsoft.meetingEnd'; + activity.value.endTime = '2021-06-05T01:02:03.0Z'; + } + + return activity; + } + + let onEventCalled; + let onDialogCalled; + this.beforeEach(function () { + onEventCalled = false; + onDialogCalled = false; + }); + + it('No MS-Teams routed activity', async function () { + const bot = new TeamsActivityHandler(); + + const activity = { type: ActivityTypes.Event, channelId: 'no-msteams' }; + + const adapter = new TestAdapter(async (context) => { + await bot.run(context); + }); + + bot.onDialog(async (context, next) => { + onDialogCalled = true; + await next(); + }); + + await adapter + .send(activity) + .then(() => { + assert(onDialogCalled, 'onDialog handler not called'); + }) + .startTest(); + }); + + it('onTeamsMeetingStart routed activity', async function () { + const bot = new TeamsActivityHandler(); + + let onTeamsMeetingStartCalled = false; + + const activity = createMeetingEventActivity(true); + + bot.onEvent(async (context, next) => { + assert(context, 'context not found'); + assert(next, 'next not found'); + onEventCalled = true; + await next(); + }); + + bot.onTeamsMeetingStartEvent(async (meeting, context, next) => { + assert(meeting, 'teamsInfo not found'); + assert(context, 'context not found'); + assert(next, 'next not found'); + assert.strictEqual(meeting, expectedMeeting); + onTeamsMeetingStartCalled = true; + await next(); + }); + + bot.onDialog(async (context, next) => { + assert(context, 'context not found'); + assert(next, 'next not found'); + onDialogCalled = true; + await next(); + }); + + const adapter = new TestAdapter(async (context) => { + await bot.run(context); + }); + + await adapter + .send(activity) + .then(() => { + assert(onTeamsMeetingStartCalled); + assert(onEventCalled, 'onConversationUpdate handler not called'); + assert(onDialogCalled, 'onDialog handler not called'); + }) + .startTest(); + }); + + it('onTeamsMeetingEnd routed activity', async function () { + const bot = new TeamsActivityHandler(); + + let onTeamsMeetingEndCalled = false; + + const activity = createMeetingEventActivity(false); + + bot.onEvent(async (context, next) => { + assert(context, 'context not found'); + assert(next, 'next not found'); + onEventCalled = true; + await next(); + }); + + bot.onTeamsMeetingEndEvent(async (meeting, context, next) => { + assert(meeting, 'teamsInfo not found'); + assert(context, 'context not found'); + assert(next, 'next not found'); + assert.strictEqual(meeting, expectedMeeting); + onTeamsMeetingEndCalled = true; + await next(); + }); + + bot.onDialog(async (context, next) => { + assert(context, 'context not found'); + assert(next, 'next not found'); + onDialogCalled = true; + await next(); + }); + + const adapter = new TestAdapter(async (context) => { + await bot.run(context); + }); + + await adapter + .send(activity) + .then(() => { + assert(onTeamsMeetingEndCalled); + assert(onEventCalled, 'onConversationUpdate handler not called'); + assert(onDialogCalled, 'onDialog handler not called'); + }) + .startTest(); + }); + }); }); diff --git a/libraries/botframework-schema/src/teams/index.ts b/libraries/botframework-schema/src/teams/index.ts index f1e9ba7c22..48f428a452 100644 --- a/libraries/botframework-schema/src/teams/index.ts +++ b/libraries/botframework-schema/src/teams/index.ts @@ -1690,13 +1690,27 @@ export type Action = 'accept' | 'decline'; /** * @interface - * Specific details of a Teams meeting. */ -export interface MeetingDetails { +interface MeetingDetailsBase { /** * @member {string} [id] The meeting's Id, encoded as a BASE64 string. */ id: string; + /** + * @member {string} [joinUrl] The URL used to join the meeting. + */ + joinUrl: string; + /** + * @member {string} [title] The title of the meeting. + */ + title: string; +} + +/** + * @interface + * Specific details of a Teams meeting. + */ +export interface MeetingDetails extends MeetingDetailsBase { /** * @member {string} [msGraphResourceId] The MsGraphResourceId, used specifically for MS Graph API calls. */ @@ -1709,14 +1723,6 @@ export interface MeetingDetails { * @member {Date} [scheduledEndTime] The meeting's scheduled end time, in UTC. */ scheduledEndTime: Date; - /** - * @member {string} [joinUrl] The URL used to join the meeting. - */ - joinUrl: string; - /** - * @member {string} [title] The title of the meeting. - */ - title: string; /** * @member {string} [type] The meeting's type. */ @@ -1741,3 +1747,35 @@ export interface MeetingInfo { */ organizer: TeamsChannelAccount; } + +/** + * @interface + */ +interface MeetingEventDetails extends MeetingDetailsBase { + /** + * @member {string} [type] The meeting's type. + */ + meetingType: string; +} + +/** + * @interface + * Specific details of a Teams meeting start event. + */ +export interface MeetingStartEventDetails extends MeetingEventDetails { + /** + * @member {Date} [startTime] Timestamp for meeting start, in UTC. + */ + startTime: Date; +} + +/** + * @interface + * Specific details of a Teams meeting end event. + */ +export interface MeetingEndEventDetails extends MeetingEventDetails { + /** + * @member {Date} [endTime] Timestamp for meeting end, in UTC. + */ + endTime: Date; +} From ab072fe00874376f3a46217f22596312913ea2de Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 16 Jun 2021 14:46:19 -0700 Subject: [PATCH 02/13] api-extractor fix --- libraries/botbuilder/etc/botbuilder.api.md | 7 +++++++ .../etc/botframework-schema.api.md | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder/etc/botbuilder.api.md b/libraries/botbuilder/etc/botbuilder.api.md index 3e47aa5420..7ddca2306d 100644 --- a/libraries/botbuilder/etc/botbuilder.api.md +++ b/libraries/botbuilder/etc/botbuilder.api.md @@ -38,6 +38,8 @@ import { INodeSocket } from 'botframework-streaming'; import { InvokeResponse } from 'botbuilder-core'; import { IReceiveRequest } from 'botframework-streaming'; import { IStreamingTransportServer } from 'botframework-streaming'; +import { MeetingEndEventDetails } from 'botbuilder-core'; +import { MeetingStartEventDetails } from 'botbuilder-core'; import { MessagingExtensionAction } from 'botbuilder-core'; import { MessagingExtensionActionResponse } from 'botbuilder-core'; import { MessagingExtensionQuery } from 'botbuilder-core'; @@ -299,6 +301,7 @@ export class StreamingHttpClient implements HttpClient { // @public export class TeamsActivityHandler extends ActivityHandler { protected dispatchConversationUpdateActivity(context: TurnContext): Promise; + protected dispatchEventActivity(context: TurnContext): Promise; protected handleAdaptiveCardAction(context: TurnContext): Promise; protected handleTeamsAppBasedLinkQuery(context: TurnContext, query: AppBasedLinkQuery): Promise; protected handleTeamsCardActionInvoke(context: TurnContext): Promise; @@ -333,6 +336,10 @@ export class TeamsActivityHandler extends ActivityHandler { // (undocumented) protected onTeamsChannelRestored(context: any): Promise; onTeamsChannelRestoredEvent(handler: (channelInfo: ChannelInfo, teamInfo: TeamInfo, context: TurnContext, next: () => Promise) => Promise): this; + protected onTeamsMeetingEnd(context: TurnContext): Promise; + onTeamsMeetingEndEvent(handler: (meeting: MeetingEndEventDetails, context: TurnContext, next: () => Promise) => Promise): this; + protected onTeamsMeetingStart(context: TurnContext): Promise; + onTeamsMeetingStartEvent(handler: (meeting: MeetingStartEventDetails, context: TurnContext, next: () => Promise) => Promise): this; protected onTeamsMembersAdded(context: TurnContext): Promise; onTeamsMembersAddedEvent(handler: (membersAdded: TeamsChannelAccount[], teamInfo: TeamInfo, context: TurnContext, next: () => Promise) => Promise): this; protected onTeamsMembersRemoved(context: TurnContext): Promise; diff --git a/libraries/botframework-schema/etc/botframework-schema.api.md b/libraries/botframework-schema/etc/botframework-schema.api.md index 97a8704d53..c1dd4162f9 100644 --- a/libraries/botframework-schema/etc/botframework-schema.api.md +++ b/libraries/botframework-schema/etc/botframework-schema.api.md @@ -875,17 +875,23 @@ export interface Meeting { role?: string; } +// Warning: (ae-forgotten-export) The symbol "MeetingDetailsBase" needs to be exported by the entry point index.d.ts +// // @public -export interface MeetingDetails { - id: string; - joinUrl: string; +export interface MeetingDetails extends MeetingDetailsBase { msGraphResourceId: string; scheduledEndTime: Date; scheduledStartTime: Date; - title: string; type: string; } +// Warning: (ae-forgotten-export) The symbol "MeetingEventDetails" needs to be exported by the entry point index.d.ts +// +// @public +export interface MeetingEndEventDetails extends MeetingEventDetails { + endTime: Date; +} + // @public export interface MeetingInfo { conversation: ConversationAccount; @@ -893,6 +899,11 @@ export interface MeetingInfo { organizer: TeamsChannelAccount; } +// @public +export interface MeetingStartEventDetails extends MeetingEventDetails { + startTime: Date; +} + // @public export interface Mention { mentioned: ChannelAccount; From 769eb2134822783104cf5345f55613bdb46d97c6 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 16 Jun 2021 16:48:16 -0700 Subject: [PATCH 03/13] camelCase event payloads --- .../botbuilder/src/teamsActivityHandler.ts | 26 +++++++++++++++++-- .../etc/botframework-schema.api.md | 7 +++-- .../botframework-schema/src/teams/index.ts | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index db9fcde1b3..3403e827e9 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -14,6 +14,7 @@ import { Channels, FileConsentCardResponse, InvokeResponse, + MeetingEventDetails, MeetingStartEventDetails, MeetingEndEventDetails, MessagingExtensionAction, @@ -985,7 +986,9 @@ export class TeamsActivityHandler extends ActivityHandler { handler: (meeting: MeetingStartEventDetails, context: TurnContext, next: () => Promise) => Promise ): this { return this.on('TeamsMeetingStart', async (context, next) => { - const meeting = context.activity.value as MeetingStartEventDetails; + const meeting = this.convertMeetingEventDetailsToCamelCase( + context.activity.value + ); await handler(meeting, context, next); }); } @@ -1000,8 +1003,27 @@ export class TeamsActivityHandler extends ActivityHandler { handler: (meeting: MeetingEndEventDetails, context: TurnContext, next: () => Promise) => Promise ): this { return this.on('TeamsMeetingEnd', async (context, next) => { - const meeting = context.activity.value as MeetingEndEventDetails; + const meeting = this.convertMeetingEventDetailsToCamelCase(context.activity.value); await handler(meeting, context, next); }); } + + private convertMeetingEventDetailsToCamelCase( + meeting: Record + ): T { + const convertedMeeting: MeetingEventDetails = { + id: meeting.Id, + joinUrl: meeting.JoinUrl, + meetingType: meeting.MeetingType, + title: meeting.Title, + }; + + if (meeting.StartTime) { + (convertedMeeting as MeetingStartEventDetails).startTime = new Date(meeting.StartTime); + } else { + (convertedMeeting as MeetingEndEventDetails).endTime = new Date(meeting.EndTime); + } + + return convertedMeeting as T; + } } diff --git a/libraries/botframework-schema/etc/botframework-schema.api.md b/libraries/botframework-schema/etc/botframework-schema.api.md index c1dd4162f9..df08091616 100644 --- a/libraries/botframework-schema/etc/botframework-schema.api.md +++ b/libraries/botframework-schema/etc/botframework-schema.api.md @@ -885,13 +885,16 @@ export interface MeetingDetails extends MeetingDetailsBase { type: string; } -// Warning: (ae-forgotten-export) The symbol "MeetingEventDetails" needs to be exported by the entry point index.d.ts -// // @public export interface MeetingEndEventDetails extends MeetingEventDetails { endTime: Date; } +// @public (undocumented) +export interface MeetingEventDetails extends MeetingDetailsBase { + meetingType: string; +} + // @public export interface MeetingInfo { conversation: ConversationAccount; diff --git a/libraries/botframework-schema/src/teams/index.ts b/libraries/botframework-schema/src/teams/index.ts index 48f428a452..e17367a084 100644 --- a/libraries/botframework-schema/src/teams/index.ts +++ b/libraries/botframework-schema/src/teams/index.ts @@ -1751,7 +1751,7 @@ export interface MeetingInfo { /** * @interface */ -interface MeetingEventDetails extends MeetingDetailsBase { +export interface MeetingEventDetails extends MeetingDetailsBase { /** * @member {string} [type] The meeting's type. */ From cbc67ac578c964fbc5df4e61e4b73b2362a35218 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 16 Jun 2021 16:49:15 -0700 Subject: [PATCH 04/13] add method comment for casing --- libraries/botbuilder/src/teamsActivityHandler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index 3403e827e9..c17f980cd0 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -1008,6 +1008,8 @@ export class TeamsActivityHandler extends ActivityHandler { }); } + // The payload from Teams comes in with TitleCase keys, so we'll convert them to camelCase + // to maintain JSON and JS standard practices. private convertMeetingEventDetailsToCamelCase( meeting: Record ): T { From adef19ad825182ee75b2951b669be95ea715257d Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 16 Jun 2021 17:10:24 -0700 Subject: [PATCH 05/13] adjust tests for casing changes --- .../tests/teamsActivityHandler.test.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libraries/botbuilder/tests/teamsActivityHandler.test.js b/libraries/botbuilder/tests/teamsActivityHandler.test.js index 9433f28aa3..d42cdad4f6 100644 --- a/libraries/botbuilder/tests/teamsActivityHandler.test.js +++ b/libraries/botbuilder/tests/teamsActivityHandler.test.js @@ -2160,21 +2160,22 @@ describe('TeamsActivityHandler', function () { }); describe('onEventActivity()', function () { - const expectedMeeting = { id: 'meetingId' }; + let meetingPayload = { id: 'meetingId' }; + // Note: Teams payload is TitleCase. function createMeetingEventActivity(start = true) { const activity = { channelId: Channels.Msteams, type: 'event', - value: expectedMeeting, + value: meetingPayload, }; if (start) { activity.name = 'application/vnd.microsoft.meetingStart'; - activity.value.startTime = '2021-06-05T00:01:02.0Z'; + activity.value.StartTime = '2021-06-05T00:01:02.0Z'; } else { activity.name = 'application/vnd.microsoft.meetingEnd'; - activity.value.endTime = '2021-06-05T01:02:03.0Z'; + activity.value.EndTime = '2021-06-05T01:02:03.0Z'; } return activity; @@ -2183,6 +2184,7 @@ describe('TeamsActivityHandler', function () { let onEventCalled; let onDialogCalled; this.beforeEach(function () { + meetingPayload = { id: 'meetingId' }; onEventCalled = false; onDialogCalled = false; }); @@ -2227,7 +2229,8 @@ describe('TeamsActivityHandler', function () { assert(meeting, 'teamsInfo not found'); assert(context, 'context not found'); assert(next, 'next not found'); - assert.strictEqual(meeting, expectedMeeting); + assert.strictEqual(meeting.id, meetingPayload.Id); + assert.strictEqual(meeting.startTime.toString(), new Date(meetingPayload.StartTime).toString()); onTeamsMeetingStartCalled = true; await next(); }); @@ -2271,7 +2274,8 @@ describe('TeamsActivityHandler', function () { assert(meeting, 'teamsInfo not found'); assert(context, 'context not found'); assert(next, 'next not found'); - assert.strictEqual(meeting, expectedMeeting); + assert.strictEqual(meeting.id, meetingPayload.Id); + assert.strictEqual(meeting.endTime.toString(), new Date(meetingPayload.EndTime).toString()); onTeamsMeetingEndCalled = true; await next(); }); From 4c550532b4354018f6dec0d927961ac790b7ee00 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 17 Jun 2021 11:45:39 -0700 Subject: [PATCH 06/13] minor doc adjustment --- libraries/botframework-schema/src/teams/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-schema/src/teams/index.ts b/libraries/botframework-schema/src/teams/index.ts index e17367a084..41c1a7d282 100644 --- a/libraries/botframework-schema/src/teams/index.ts +++ b/libraries/botframework-schema/src/teams/index.ts @@ -1753,7 +1753,7 @@ export interface MeetingInfo { */ export interface MeetingEventDetails extends MeetingDetailsBase { /** - * @member {string} [type] The meeting's type. + * @member {string} [meetingType] The meeting's type. */ meetingType: string; } From 8b39ff83130224fc4430723c73ac149b7afe17c8 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 17 Jun 2021 11:52:15 -0700 Subject: [PATCH 07/13] don't shortcut parent dispatchEventActivity() --- libraries/botbuilder/src/teamsActivityHandler.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index c17f980cd0..fed1a006f0 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -944,9 +944,11 @@ export class TeamsActivityHandler extends ActivityHandler { if (context.activity.channelId == Channels.Msteams) { switch (context.activity.name) { case 'application/vnd.microsoft.meetingStart': - return this.onTeamsMeetingStart(context); + await this.onTeamsMeetingStart(context); + break; case 'application/vnd.microsoft.meetingEnd': - return this.onTeamsMeetingEnd(context); + await this.onTeamsMeetingEnd(context); + break; } } From f2645e603ed14b9d5be1a0b626410b56aee53b45 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 17 Jun 2021 16:17:05 -0700 Subject: [PATCH 08/13] add err --- libraries/botbuilder/src/teamsActivityHandler.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index fed1a006f0..3d62144a38 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -1024,8 +1024,12 @@ export class TeamsActivityHandler extends ActivityHandler { if (meeting.StartTime) { (convertedMeeting as MeetingStartEventDetails).startTime = new Date(meeting.StartTime); - } else { + } else if (meeting.EndTime) { (convertedMeeting as MeetingEndEventDetails).endTime = new Date(meeting.EndTime); + } else { + throw new Error( + `Invalid meeting. It does not include StartTime or EndTime: ${JSON.stringify(meeting, null, 2)}` + ); } return convertedMeeting as T; From 8eda7e64da193d1674f9e4c69a4120318934e717 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 18 Jun 2021 09:38:49 -0700 Subject: [PATCH 09/13] Revert "don't shortcut parent dispatchEventActivity()" This reverts commit 8b39ff83130224fc4430723c73ac149b7afe17c8. --- libraries/botbuilder/src/teamsActivityHandler.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index 3d62144a38..a63c0e4b0b 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -944,11 +944,9 @@ export class TeamsActivityHandler extends ActivityHandler { if (context.activity.channelId == Channels.Msteams) { switch (context.activity.name) { case 'application/vnd.microsoft.meetingStart': - await this.onTeamsMeetingStart(context); - break; + return this.onTeamsMeetingStart(context); case 'application/vnd.microsoft.meetingEnd': - await this.onTeamsMeetingEnd(context); - break; + return this.onTeamsMeetingEnd(context); } } From 52a30baa151efb8c4fda872012c935612c9cfabd Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 18 Jun 2021 12:04:38 -0700 Subject: [PATCH 10/13] PR review adjustments --- .../botbuilder-core/src/activityHandler.ts | 40 +++++----- .../src/activityHandlerBase.ts | 34 ++++----- libraries/botbuilder/package.json | 1 + .../botbuilder/src/teamsActivityHandler.ts | 74 +++++++++++-------- .../tests/teamsActivityHandler.test.js | 4 +- yarn.lock | 2 +- 6 files changed, 83 insertions(+), 72 deletions(-) diff --git a/libraries/botbuilder-core/src/activityHandler.ts b/libraries/botbuilder-core/src/activityHandler.ts index 7bc26de394..8f97594581 100644 --- a/libraries/botbuilder-core/src/activityHandler.ts +++ b/libraries/botbuilder-core/src/activityHandler.ts @@ -457,7 +457,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to use custom logic for emitting events. + * Override this method to use custom logic for emitting events. * * The default logic is to call any handlers registered via [onTurn](xref:botbuilder-core.ActivityHandler.onTurn), * and then continue by calling [ActivityHandlerBase.onTurnActivity](xref:botbuilder-core.ActivityHandlerBase.onTurnActivity). @@ -474,7 +474,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onMessage](xref:botbuilder-core.ActivityHandler.onMessage), @@ -489,7 +489,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * The default logic is to check for a signIn invoke and handle that * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ @@ -531,7 +531,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. */ protected async onSignInInvoke(context: TurnContext): Promise { throw new InvokeException(StatusCodes.NOT_IMPLEMENTED); @@ -556,7 +556,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onEndOfConversationActivity](xref:botbuilder-core.ActivityHandler.onMessage), @@ -572,7 +572,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onTypingActivity](xref:botbuilder-core.ActivityHandler.onTypingActivity), @@ -588,7 +588,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onInstallationUpdateActivity](xref:botbuilder-core.ActivityHandler.onInstallationUpdateActivity), @@ -624,7 +624,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels or to add + * Override this method to support channel-specific behavior across multiple channels or to add * custom conversation update sub-type events. * * The default logic is: @@ -651,7 +651,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onInstallationUpdateAdd](xref:botbuilder-core.ActivityHandler.onInstallationUpdateAdd), @@ -667,7 +667,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onInstallationUpdateRemove](xref:botbuilder-core.ActivityHandler.onInstallationUpdateRemove), @@ -683,7 +683,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onUnrecognizedActivityType](xref:botbuilder-core.ActivityHandler.onUnrecognizedActivityType), @@ -743,7 +743,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onConversationUpdate](xref:botbuilder-core.ActivityHandler.onConversationUpdate), @@ -762,7 +762,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels or to add + * Override this method to support channel-specific behavior across multiple channels or to add * custom conversation update sub-type events. * * The default logic is: @@ -786,7 +786,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onMessageReaction](xref:botbuilder-core.ActivityHandler.onMessageReaction), @@ -806,7 +806,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onReactionsAdded](xref:botbuilder-core.ActivityHandler.onReactionsAdded), @@ -823,7 +823,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onReactionsRemoved](xref:botbuilder-core.ActivityHandler.onReactionsRemoved), @@ -842,7 +842,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels or to add + * Override this method to support channel-specific behavior across multiple channels or to add * custom message reaction sub-type events. * * The default logic is: @@ -864,7 +864,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels. + * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onEvent](xref:botbuilder-core.ActivityHandler.onEvent), @@ -883,7 +883,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels or to add custom event sub-type events. + * Override this method to support channel-specific behavior across multiple channels or to add custom event sub-type events. * For certain channels, such as Web Chat and custom Direct Line clients, developers can emit custom event activities from the client. * * The default logic is: @@ -905,7 +905,7 @@ export class ActivityHandler extends ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to use custom logic for emitting events. + * Override this method to use custom logic for emitting events. * * The default logic is to call any handlers registered via [onDialog](xref:botbuilder-core.ActivityHandler.onDialog), * and then complete the event emission process. diff --git a/libraries/botbuilder-core/src/activityHandlerBase.ts b/libraries/botbuilder-core/src/activityHandlerBase.ts index 40815037de..b86bb9f13e 100644 --- a/libraries/botbuilder-core/src/activityHandlerBase.ts +++ b/libraries/botbuilder-core/src/activityHandlerBase.ts @@ -6,7 +6,7 @@ import { ActivityTypes, ChannelAccount, MessageReaction, TurnContext } from '.'; import { InvokeResponse } from './invokeResponse'; import { StatusCodes } from './statusCodes'; -// This key is exported internally so that subclassed ActivityHandlers and BotAdapters will not overwrite any already set InvokeResponses. +// This key is exported internally so that subclassed ActivityHandlers and BotAdapters will not override any already set InvokeResponses. export const INVOKE_RESPONSE_KEY = Symbol('invokeResponse'); /** @@ -37,7 +37,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to use custom logic for emitting events. + * Override this method to use custom logic for emitting events. * * The default logic is to call any type-specific and sub-type handlers registered via * the various _on event_ methods. Type-specific events are defined for: @@ -97,7 +97,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _message_ handlers and then continue the event + * Override this method to run registered _message_ handlers and then continue the event * emission process. */ protected async onMessageActivity(context: TurnContext): Promise { @@ -110,7 +110,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _conversation update_ handlers and then continue the event + * Override this method to run registered _conversation update_ handlers and then continue the event * emission process. * * The default logic is: @@ -145,7 +145,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _message reaction_ handlers and then continue the event + * Override this method to run registered _message reaction_ handlers and then continue the event * emission process. * * The default logic is: @@ -168,7 +168,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _event_ handlers and then continue the event + * Override this method to run registered _event_ handlers and then continue the event * emission process. */ protected async onEventActivity(context: TurnContext): Promise { @@ -181,7 +181,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to handle particular invoke calls. + * Override this method to handle particular invoke calls. */ protected async onInvokeActivity(context: TurnContext): Promise { return { status: StatusCodes.NOT_IMPLEMENTED }; @@ -193,7 +193,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _end of conversation_ handlers and then continue the event + * Override this method to run registered _end of conversation_ handlers and then continue the event * emission process. */ protected async onEndOfConversationActivity(context: TurnContext): Promise { @@ -206,7 +206,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _typing_ handlers and then continue the event + * Override this method to run registered _typing_ handlers and then continue the event * emission process. */ protected async onTypingActivity(context: TurnContext): Promise { @@ -219,7 +219,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _installationupdate_ handlers and then continue the event + * Override this method to run registered _installationupdate_ handlers and then continue the event * emission process. */ protected async onInstallationUpdateActivity(context: TurnContext): Promise { @@ -241,7 +241,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _installationupdateadd_ handlers and then continue the event + * Override this method to run registered _installationupdateadd_ handlers and then continue the event * emission process. */ protected async onInstallationUpdateAddActivity(context: TurnContext): Promise { @@ -254,7 +254,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _installationupdateremove_ handlers and then continue the event + * Override this method to run registered _installationupdateremove_ handlers and then continue the event * emission process. */ protected async onInstallationUpdateRemoveActivity(context: TurnContext): Promise { @@ -267,7 +267,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _unrecognized_ handlers and then continue the event + * Override this method to run registered _unrecognized_ handlers and then continue the event * emission process. */ protected async onUnrecognizedActivity(context: TurnContext): Promise { @@ -282,7 +282,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _members added_ handlers and then continue the event + * Override this method to run registered _members added_ handlers and then continue the event * emission process. */ protected async onMembersAddedActivity(membersAdded: ChannelAccount[], context: TurnContext): Promise { @@ -297,7 +297,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _members removed_ handlers and then continue the event + * Override this method to run registered _members removed_ handlers and then continue the event * emission process. */ protected async onMembersRemovedActivity(membersRemoved: ChannelAccount[], context: TurnContext): Promise { @@ -312,7 +312,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _reactions added_ handlers and then continue the event + * Override this method to run registered _reactions added_ handlers and then continue the event * emission process. */ protected async onReactionsAddedActivity(reactionsAdded: MessageReaction[], context: TurnContext): Promise { @@ -327,7 +327,7 @@ export class ActivityHandlerBase { * @param context The context object for the current turn. * * @remarks - * Overwrite this method to run registered _reactions removed_ handlers and then continue the event + * Override this method to run registered _reactions removed_ handlers and then continue the event * emission process. */ protected async onReactionsRemovedActivity( diff --git a/libraries/botbuilder/package.json b/libraries/botbuilder/package.json index 68a8cc6fc8..2ce69ceb57 100644 --- a/libraries/botbuilder/package.json +++ b/libraries/botbuilder/package.json @@ -38,6 +38,7 @@ "filenamify": "^4.1.0", "fs-extra": "^7.0.1", "htmlparser2": "^6.0.1", + "runtypes": "6.3.0", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index a63c0e4b0b..ac511ddd5f 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -36,6 +36,23 @@ import { verifyStateOperationName, } from 'botbuilder-core'; import { TeamsInfo } from './teamsInfo'; +import * as t from 'runtypes'; + +const TeamsMeetingStartT = t.Record({ + Id: t.String, + JoinUrl: t.String, + MeetingType: t.String, + Title: t.String, + StartTime: t.String, +}); + +const TeamsMeetingEndT = t.Record({ + Id: t.String, + JoinUrl: t.String, + MeetingType: t.String, + Title: t.String, + EndTime: t.String, +}); /** * Adds support for Microsoft Teams specific events and interactions. @@ -936,12 +953,12 @@ export class TeamsActivityHandler extends ActivityHandler { * @returns A promise that represents the work queued. * * @remarks - * Overwrite this method to support channel-specific behavior across multiple channels or to add + * Override this method to support channel-specific behavior across multiple channels or to add * custom event sub-type events. */ protected async dispatchEventActivity(context: TurnContext): Promise { await this.handle(context, 'Event', async () => { - if (context.activity.channelId == Channels.Msteams) { + if (context.activity.channelId === Channels.Msteams) { switch (context.activity.name) { case 'application/vnd.microsoft.meetingStart': return this.onTeamsMeetingStart(context); @@ -986,10 +1003,18 @@ export class TeamsActivityHandler extends ActivityHandler { handler: (meeting: MeetingStartEventDetails, context: TurnContext, next: () => Promise) => Promise ): this { return this.on('TeamsMeetingStart', async (context, next) => { - const meeting = this.convertMeetingEventDetailsToCamelCase( - context.activity.value + const meeting = TeamsMeetingStartT.check(context.activity.value); + await handler( + { + id: meeting.Id, + joinUrl: meeting.JoinUrl, + meetingType: meeting.MeetingType, + startTime: new Date(meeting.StartTime), + title: meeting.Title, + }, + context, + next ); - await handler(meeting, context, next); }); } @@ -1003,33 +1028,18 @@ export class TeamsActivityHandler extends ActivityHandler { handler: (meeting: MeetingEndEventDetails, context: TurnContext, next: () => Promise) => Promise ): this { return this.on('TeamsMeetingEnd', async (context, next) => { - const meeting = this.convertMeetingEventDetailsToCamelCase(context.activity.value); - await handler(meeting, context, next); - }); - } - - // The payload from Teams comes in with TitleCase keys, so we'll convert them to camelCase - // to maintain JSON and JS standard practices. - private convertMeetingEventDetailsToCamelCase( - meeting: Record - ): T { - const convertedMeeting: MeetingEventDetails = { - id: meeting.Id, - joinUrl: meeting.JoinUrl, - meetingType: meeting.MeetingType, - title: meeting.Title, - }; - - if (meeting.StartTime) { - (convertedMeeting as MeetingStartEventDetails).startTime = new Date(meeting.StartTime); - } else if (meeting.EndTime) { - (convertedMeeting as MeetingEndEventDetails).endTime = new Date(meeting.EndTime); - } else { - throw new Error( - `Invalid meeting. It does not include StartTime or EndTime: ${JSON.stringify(meeting, null, 2)}` + const meeting = TeamsMeetingEndT.check(context.activity.value); + await handler( + { + id: meeting.Id, + joinUrl: meeting.JoinUrl, + meetingType: meeting.MeetingType, + endTime: new Date(meeting.EndTime), + title: meeting.Title, + }, + context, + next ); - } - - return convertedMeeting as T; + }); } } diff --git a/libraries/botbuilder/tests/teamsActivityHandler.test.js b/libraries/botbuilder/tests/teamsActivityHandler.test.js index d42cdad4f6..e5718b32f3 100644 --- a/libraries/botbuilder/tests/teamsActivityHandler.test.js +++ b/libraries/botbuilder/tests/teamsActivityHandler.test.js @@ -2160,7 +2160,7 @@ describe('TeamsActivityHandler', function () { }); describe('onEventActivity()', function () { - let meetingPayload = { id: 'meetingId' }; + let meetingPayload; // Note: Teams payload is TitleCase. function createMeetingEventActivity(start = true) { @@ -2184,7 +2184,7 @@ describe('TeamsActivityHandler', function () { let onEventCalled; let onDialogCalled; this.beforeEach(function () { - meetingPayload = { id: 'meetingId' }; + meetingPayload = { Id: 'meetingId' }; onEventCalled = false; onDialogCalled = false; }); diff --git a/yarn.lock b/yarn.lock index 4ff58e5e9d..a9a92f2a85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10876,7 +10876,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -runtypes@~6.3.0: +runtypes@6.3.0, runtypes@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/runtypes/-/runtypes-6.3.0.tgz#bd88392c21f471bd45591d5eabaa4644ca7cdf3c" integrity sha512-FTNUs13CIrCTjReBOaeY/8EY1LYIQVkkwyE9z5MCjZe9uew9/8TRbWF1PcTczgTFfGBjkjUKeedFWU2O3ExjPg== From c56cc8d32887ffb3122e8d27d0ffba1986300020 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 18 Jun 2021 12:11:47 -0700 Subject: [PATCH 11/13] tilde --- libraries/botbuilder/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder/package.json b/libraries/botbuilder/package.json index 2ce69ceb57..eff20ba31d 100644 --- a/libraries/botbuilder/package.json +++ b/libraries/botbuilder/package.json @@ -38,7 +38,7 @@ "filenamify": "^4.1.0", "fs-extra": "^7.0.1", "htmlparser2": "^6.0.1", - "runtypes": "6.3.0", + "runtypes": "~6.3.0", "uuid": "^8.3.2" }, "devDependencies": { From 0d464d56bca98e2102dbbfe787b292759679b753 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 18 Jun 2021 12:50:14 -0700 Subject: [PATCH 12/13] make tests work w/ runtypes --- libraries/botbuilder/tests/teamsActivityHandler.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder/tests/teamsActivityHandler.test.js b/libraries/botbuilder/tests/teamsActivityHandler.test.js index e5718b32f3..86c6f33ea3 100644 --- a/libraries/botbuilder/tests/teamsActivityHandler.test.js +++ b/libraries/botbuilder/tests/teamsActivityHandler.test.js @@ -2184,7 +2184,12 @@ describe('TeamsActivityHandler', function () { let onEventCalled; let onDialogCalled; this.beforeEach(function () { - meetingPayload = { Id: 'meetingId' }; + meetingPayload = { + Id: 'meetingId', + JoinUrl: 'https://joinUrl', + MeetingType: 'Scheduled', + title: 'someTitle' + }; onEventCalled = false; onDialogCalled = false; }); From 00c66d68297c055cca6481cf07dce13fce63aac4 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 18 Jun 2021 13:15:51 -0700 Subject: [PATCH 13/13] cap fix --- libraries/botbuilder/tests/teamsActivityHandler.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder/tests/teamsActivityHandler.test.js b/libraries/botbuilder/tests/teamsActivityHandler.test.js index 86c6f33ea3..caa68c708d 100644 --- a/libraries/botbuilder/tests/teamsActivityHandler.test.js +++ b/libraries/botbuilder/tests/teamsActivityHandler.test.js @@ -2188,7 +2188,7 @@ describe('TeamsActivityHandler', function () { Id: 'meetingId', JoinUrl: 'https://joinUrl', MeetingType: 'Scheduled', - title: 'someTitle' + Title: 'someTitle', }; onEventCalled = false; onDialogCalled = false;