Skip to content

Commit

Permalink
port: [#4530] Add support for meeting participants added/removed even…
Browse files Browse the repository at this point in the history
…ts (#4538)

* add meeting participants events

* add unit tests

* fix api signatures

---------

Co-authored-by: JhontSouth <jhonatan.sandoval@southworks.com>
  • Loading branch information
ceciliaavila and JhontSouth authored Oct 3, 2023
1 parent cbb187d commit 10b9fe0
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 0 deletions.
5 changes: 5 additions & 0 deletions libraries/botbuilder/etc/botbuilder.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { IStreamingTransportServer } from 'botframework-streaming';
import { MeetingEndEventDetails } from 'botbuilder-core';
import { MeetingNotification } from 'botbuilder-core';
import { MeetingNotificationResponse } from 'botbuilder-core';
import { MeetingParticipantsEventDetails } from 'botbuilder-core';
import { MeetingStartEventDetails } from 'botbuilder-core';
import { MessagingExtensionAction } from 'botbuilder-core';
import { MessagingExtensionActionResponse } from 'botbuilder-core';
Expand Down Expand Up @@ -404,6 +405,10 @@ export class TeamsActivityHandler extends ActivityHandler {
onTeamsChannelRestoredEvent(handler: (channelInfo: ChannelInfo, teamInfo: TeamInfo, context: TurnContext, next: () => Promise<void>) => Promise<void>): this;
protected onTeamsMeetingEnd(context: TurnContext): Promise<void>;
onTeamsMeetingEndEvent(handler: (meeting: MeetingEndEventDetails, context: TurnContext, next: () => Promise<void>) => Promise<void>): this;
protected onTeamsMeetingParticipantsJoin(context: TurnContext): Promise<void>;
onTeamsMeetingParticipantsJoinEvent(handler: (meeting: MeetingParticipantsEventDetails, context: TurnContext, next: () => Promise<void>) => Promise<void>): this;
protected onTeamsMeetingParticipantsLeave(context: TurnContext): Promise<void>;
onTeamsMeetingParticipantsLeaveEvent(handler: (meeting: MeetingParticipantsEventDetails, context: TurnContext, next: () => Promise<void>) => Promise<void>): this;
protected onTeamsMeetingStart(context: TurnContext): Promise<void>;
onTeamsMeetingStartEvent(handler: (meeting: MeetingStartEventDetails, context: TurnContext, next: () => Promise<void>) => Promise<void>): this;
protected onTeamsMembersAdded(context: TurnContext): Promise<void>;
Expand Down
77 changes: 77 additions & 0 deletions libraries/botbuilder/src/teamsActivityHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
tokenExchangeOperationName,
TurnContext,
verifyStateOperationName,
MeetingParticipantsEventDetails,
} from 'botbuilder-core';
import { ReadReceiptInfo } from 'botframework-connector';
import { TeamsInfo } from './teamsInfo';
Expand Down Expand Up @@ -1193,6 +1194,10 @@ export class TeamsActivityHandler extends ActivityHandler {
return this.onTeamsMeetingStart(context);
case 'application/vnd.microsoft.meetingEnd':
return this.onTeamsMeetingEnd(context);
case 'application/vnd.microsoft.meetingParticipantJoin':
return this.onTeamsMeetingParticipantsJoin(context);
case 'application/vnd.microsoft.meetingParticipantLeave':
return this.onTeamsMeetingParticipantsLeave(context);
}
}

Expand Down Expand Up @@ -1232,6 +1237,28 @@ export class TeamsActivityHandler extends ActivityHandler {
await this.handle(context, 'TeamsReadReceipt', this.defaultNextEvent(context));
}

/**
* Invoked when a Meeting Participant Join event activity is received from the connector.
* Override this in a derived class to provide logic for when a meeting participant is joined.
*
* @param context The context for this turn.
* @returns A promise that represents the work queued.
*/
protected async onTeamsMeetingParticipantsJoin(context: TurnContext): Promise<void> {
await this.handle(context, 'TeamsMeetingParticipantsJoin', this.defaultNextEvent(context));
}

/**
* Invoked when a Meeting Participant Leave event activity is received from the connector.
* Override this in a derived class to provide logic for when a meeting participant is left.
*
* @param context The context for this turn.
* @returns A promise that represents the work queued.
*/
protected async onTeamsMeetingParticipantsLeave(context: TurnContext): Promise<void> {
await this.handle(context, 'TeamsMeetingParticipantsLeave', this.defaultNextEvent(context));
}

/**
* Registers a handler for when a Teams meeting starts.
*
Expand Down Expand Up @@ -1296,4 +1323,54 @@ export class TeamsActivityHandler extends ActivityHandler {
await handler(new ReadReceiptInfo(receiptInfo.lastReadMessageId), context, next);
});
}

/**
* Registers a handler for when a Teams meeting participant join.
*
* @param handler A callback that handles Meeting Participant Join events.
* @returns A promise that represents the work queued.
*/
onTeamsMeetingParticipantsJoinEvent(
handler: (
meeting: MeetingParticipantsEventDetails,
context: TurnContext,
next: () => Promise<void>
) => Promise<void>
): this {
return this.on('TeamsMeetingParticipantsJoin', async (context, next) => {
const meeting = context.activity.value;
await handler(
{
members: meeting?.members,
},
context,
next
);
});
}

/**
* Registers a handler for when a Teams meeting participant leave.
*
* @param handler A callback that handles Meeting Participant Leave events.
* @returns A promise that represents the work queued.
*/
onTeamsMeetingParticipantsLeaveEvent(
handler: (
meeting: MeetingParticipantsEventDetails,
context: TurnContext,
next: () => Promise<void>
) => Promise<void>
): this {
return this.on('TeamsMeetingParticipantsLeave', async (context, next) => {
const meeting = context.activity.value;
await handler(
{
members: meeting?.members,
},
context,
next
);
});
}
}
112 changes: 112 additions & 0 deletions libraries/botbuilder/tests/teamsActivityHandler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,34 @@ describe('TeamsActivityHandler', function () {
return activity;
}

function createMeetingParticipantEventActivity(join = true) {
const value = {
members: [
{
user: {
id: 'id',
name: 'name',
},
meeting: {
role: 'role',
inMeeting: join,
},
},
],
};

const activity = {
channelId: Channels.Msteams,
type: 'event',
value: value,
name: join
? 'application/vnd.microsoft.meetingParticipantJoin'
: 'application/vnd.microsoft.meetingParticipantLeave',
};

return activity;
}

let onEventCalled;
let onDialogCalled;
this.beforeEach(function () {
Expand Down Expand Up @@ -2797,5 +2825,89 @@ describe('TeamsActivityHandler', function () {
})
.startTest();
});

it('onTeamsMeetingParticipantsJoin routed activity', async function () {
let onTeamsMeetingParticipantsJoinCalled = false;
const bot = new TeamsActivityHandler();
const activity = createMeetingParticipantEventActivity();

bot.onEvent(async (context, next) => {
assert(context, 'context not found');
assert(next, 'next not found');
onEventCalled = true;
await next();
});

bot.onTeamsMeetingParticipantsJoinEvent(async (meeting, context, next) => {
assert(meeting, 'meeting not found');
assert(context, 'context not found');
assert(next, 'next not found');
assert.deepStrictEqual(meeting, activity.value);
onTeamsMeetingParticipantsJoinCalled = 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(onTeamsMeetingParticipantsJoinCalled);
assert(onEventCalled, 'onConversationUpdate handler not called');
assert(onDialogCalled, 'onDialog handler not called');
})
.startTest();
});

it('onTeamsMeetingParticipantsLeave routed activity', async function () {
let onTeamsMeetingParticipantsLeaveCalled = false;
const bot = new TeamsActivityHandler();
const activity = createMeetingParticipantEventActivity(false);

bot.onEvent(async (context, next) => {
assert(context, 'context not found');
assert(next, 'next not found');
onEventCalled = true;
await next();
});

bot.onTeamsMeetingParticipantsLeaveEvent(async (meeting, context, next) => {
assert(meeting, 'meeting not found');
assert(context, 'context not found');
assert(next, 'next not found');
assert.deepStrictEqual(meeting, activity.value);
onTeamsMeetingParticipantsLeaveCalled = 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(onTeamsMeetingParticipantsLeaveCalled);
assert(onEventCalled, 'onConversationUpdate handler not called');
assert(onDialogCalled, 'onDialog handler not called');
})
.startTest();
});
});
});
17 changes: 17 additions & 0 deletions libraries/botframework-schema/etc/botframework-schema.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,11 @@ export interface MeetingNotificationResponse {
recipientsFailureInfo?: MeetingNotificationRecipientFailureInfo[];
}

// @public
export interface MeetingParticipantsEventDetails {
members: TeamsMeetingMember[];
}

// @public
export interface MeetingStageSurface<T> {
content: T;
Expand Down Expand Up @@ -1805,6 +1810,12 @@ export interface TeamsMeetingInfo {
id?: string;
}

// @public
export interface TeamsMeetingMember {
meeting: UserMeetingDetails;
user: TeamsChannelAccount;
}

// @public
export interface TeamsMeetingParticipant {
conversation?: ConversationAccount;
Expand Down Expand Up @@ -1949,6 +1960,12 @@ export type Type3 = MessagingExtensionResultType;
// @public
export type UserIdentityType = 'aadUser' | 'onPremiseAadUser' | 'anonymousGuest' | 'federatedUser';

// @public
export interface UserMeetingDetails {
inMeeting: boolean;
role: string;
}

// @public
export interface VideoCard {
aspect: string;
Expand Down
41 changes: 41 additions & 0 deletions libraries/botframework-schema/src/teams/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2029,3 +2029,44 @@ export interface MeetingNotificationResponse {
*/
recipientsFailureInfo?: MeetingNotificationRecipientFailureInfo[];
}

/**
* @interface
* Specific details about the meeting participants.
*/
export interface MeetingParticipantsEventDetails {
/**
* @member {TeamsMeetingMember[]} [members] The participants info.
*/
members: TeamsMeetingMember[];
}

/**
* @interface
* Specific details about the meeting participants.
*/
export interface TeamsMeetingMember {
/**
* @member {TeamsChannelAccount} [user] The participant account.
*/
user: TeamsChannelAccount;
/**
* @member {UserMeetingDetails} [meeting] The participants info.
*/
meeting: UserMeetingDetails;
}

/**
* @interface
* Specific details of a user in a Teams meeting.
*/
export interface UserMeetingDetails {
/**
* @member {boolean} [inMeeting] The user in meeting indicator.
*/
inMeeting: boolean;
/**
* @member {string} [role] The user's role.
*/
role: string;
}

0 comments on commit 10b9fe0

Please sign in to comment.