Skip to content

Commit

Permalink
feat: add getMeetingInfo to TeamsInfo (#3738)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdrichardson authored Jun 14, 2021
1 parent a8731af commit ec197fe
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 5 deletions.
1 change: 1 addition & 0 deletions libraries/botbuilder/etc/botbuilder.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ export function teamsGetTenant(activity: Activity): TenantInfo | null;

// @public
export class TeamsInfo {
static getMeetingInfo(context: TurnContext, meetingId?: string): Promise<TeamsMeetingInfo>;
static getMeetingParticipant(context: TurnContext, meetingId?: string, participantId?: string, tenantId?: string): Promise<TeamsMeetingParticipant>;
static getMember(context: TurnContext, userId: string): Promise<TeamsChannelAccount>;
static getMembers(context: TurnContext): Promise<TeamsChannelAccount[]>;
Expand Down
32 changes: 29 additions & 3 deletions libraries/botbuilder/src/teamsInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ConversationParameters,
ConversationReference,
TeamsMeetingParticipant,
TeamsMeetingInfo,
} from 'botbuilder-core';
import { ConnectorClient, TeamsConnectorClient, TeamsConnectorModels } from 'botframework-connector';

Expand Down Expand Up @@ -53,7 +54,7 @@ export class TeamsInfo {

if (meetingId == null) {
const meeting = teamsGetTeamMeetingInfo(activity);
meetingId = meeting ? meeting.id : undefined;
meetingId = meeting?.id;
}

if (!meetingId) {
Expand All @@ -62,7 +63,7 @@ export class TeamsInfo {

if (participantId == null) {
const from = activity.from;
participantId = from ? from.aadObjectId : undefined;
participantId = from?.aadObjectId;
}

if (!participantId) {
Expand All @@ -73,14 +74,39 @@ export class TeamsInfo {
// wants to disable defaulting of tenant ID they can pass `null`.
if (tenantId === undefined) {
const tenant = teamsGetTenant(activity);
tenantId = tenant ? tenant.id : undefined;
tenantId = tenant?.id;
}

return this.getTeamsConnectorClient(context).teams.fetchMeetingParticipant(meetingId, participantId, {
tenantId,
});
}

/**
* Gets the information for the given meeting id.
* @param context The [TurnContext](xref:botbuilder-core.TurnContext) for this turn.
* @param meetingId The BASE64-encoded id of the Teams meeting.
* @returns The [TeamsMeetingInfo](xref:botbuilder-core.TeamsMeetingInfo) fetched
*/
public static async getMeetingInfo(context: TurnContext, meetingId?: string): Promise<TeamsMeetingInfo> {
if (!context) {
throw new Error('context is required.');
}

const activity = context.activity;

if (meetingId == null) {
const meeting = teamsGetTeamMeetingInfo(activity);
meetingId = meeting?.id;
}

if (!meetingId) {
throw new Error('meetingId or TurnContext containing meetingId is required.');
}

return this.getTeamsConnectorClient(context).teams.fetchMeetingInfo(meetingId);
}

/**
* Gets the details for the given team id. This only works in teams scoped conversations.
* @param context The [TurnContext](xref:botbuilder-core.TurnContext) for this turn.
Expand Down
94 changes: 94 additions & 0 deletions libraries/botbuilder/tests/teamsInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,100 @@ describe('TeamsInfo', function () {
});
});

describe('getMeetingInfo', function () {
const context = new TestContext(teamActivity);

it('should work with correct arguments-meetingId in context', async function () {
const details = {
organizer: {
id: teamActivity.from.id,
name: teamActivity.from.name,
objectId: 'User-One-Object-Id',
givenName: 'User',
surname: 'One',
email: '[email protected]',
userPrincipalName: '[email protected]',
tenantId: teamActivity.conversation.tenantId,
},
details: {
id: 'meeting-id',
msGraphResourceId: 'msGraph-id',
scheduledStartTime: new Date('Thu Jun 10 2021 15:02:32 GMT-0700'),
scheduledEndTime: new Date('Thu Jun 10 2021 16:02:32 GMT-0700'),
joinUrl: 'https://teams.microsoft.com/l/meetup-join/someEncodedMeetingString',
title: 'Fake meeting',
type: 'Scheduled',
},
conversation: {
id: teamActivity.conversation.id,
},
};

const { expectedAuthHeader, expectation: fetchOauthToken } = nockOauth();

const fetchExpectation = nock('https://smba.trafficmanager.net/amer')
.get('/v1/meetings/19%3AmeetingId')
.matchHeader('Authorization', expectedAuthHeader)
.reply(200, details);

const fetchedDetails = await TeamsInfo.getMeetingInfo(context);

assert(fetchOauthToken.isDone());
assert(fetchExpectation.isDone());

assert.deepStrictEqual(fetchedDetails, details);
});

it('should work with correct arguments-meetingId passed in', async function () {
const details = {
organizer: {
id: teamActivity.from.id,
name: teamActivity.from.name,
objectId: 'User-One-Object-Id',
givenName: 'User',
surname: 'One',
email: '[email protected]',
userPrincipalName: '[email protected]',
tenantId: teamActivity.conversation.tenantId,
},
details: {
id: 'meeting-id',
msGraphResourceId: 'msGraph-id',
scheduledStartTime: new Date('Thu Jun 10 2021 15:02:32 GMT-0700'),
scheduledEndTime: new Date('Thu Jun 10 2021 16:02:32 GMT-0700'),
joinUrl: 'https://teams.microsoft.com/l/meetup-join/someEncodedMeetingString',
title: 'Fake meeting',
type: 'Scheduled',
},
conversation: {
id: teamActivity.conversation.id,
},
};

const { expectedAuthHeader, expectation: fetchOauthToken } = nockOauth();

const fetchExpectation = nock('https://smba.trafficmanager.net/amer')
.get('/v1/meetings/meeting-id')
.matchHeader('Authorization', expectedAuthHeader)
.reply(200, details);

const fetchedDetails = await TeamsInfo.getMeetingInfo(context, details.details.id);

assert(fetchOauthToken.isDone());
assert(fetchExpectation.isDone());

assert.deepStrictEqual(fetchedDetails, details);
});

it('should throw error for missing context', async function () {
await assert.rejects(TeamsInfo.getMeetingInfo(), Error('context is required.'));
});

it('should throw error for missing meetingId', async function () {
await assert.rejects(TeamsInfo.getMeetingInfo({ activity: {} }), Error('meetingId or TurnContext containing meetingId is required.'));
});
});

describe('getTeamMembers()', function () {
it('should error in 1-on-1 chat', async function () {
const context = new TestContext(oneOnOneActivity);
Expand Down
21 changes: 20 additions & 1 deletion libraries/botframework-connector/src/teams/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { HttpResponse, ServiceClientOptions, RequestOptionsBase } from '@azure/ms-rest-js';
import { ConversationList, TeamDetails, TeamsMeetingParticipant } from 'botframework-schema';
import { ConversationList, TeamDetails, TeamsMeetingInfo, TeamsMeetingParticipant } from 'botframework-schema';

/**
* @interface
Expand Down Expand Up @@ -99,3 +99,22 @@ export interface TeamsFetchMeetingParticipantOptionalParams extends RequestOptio
*/
tenantId?: string;
}

/**
* Contains response data for the fetchMeetingInfo operation.
*/
export type TeamsMeetingInfoResponse = TeamsMeetingInfo & {
/**
* The underlying HTTP response.
*/
_response: HttpResponse & {
/**
* The response body as text (string format)
*/
bodyAsText: string;
/**
* The response body as parsed JSON or XML
*/
parsedBody: TeamsMeetingParticipant;
};
};
83 changes: 83 additions & 0 deletions libraries/botframework-connector/src/teams/models/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,89 @@ export const TeamsMeetingParticipant: msRest.CompositeMapper = {
},
};

export const TeamsMeetingInfo: msRest.CompositeMapper = {
serializedName: 'TeamsMeetingInfo',
type: {
name: 'Composite',
className: 'TeamsMeetingInfo',
modelProperties: {
details: {
serializedName: 'details',
type: {
name: 'Composite',
className: 'TeamsMeetingDetails',
},
},
conversation: {
serializedName: 'conversation',
type: {
name: 'Composite',
className: 'MessageActionsPayloadConversation',
},
},
organizer: {
serializedName: 'organizer',
type: {
name: 'Composite',
className: 'TeamsChannelAccount',
},
},
},
},
};

export const TeamsMeetingDetails: msRest.CompositeMapper = {
serializedName: 'TeamsMeetingDetails',
type: {
name: 'Composite',
className: 'TeamsMeetingDetails',
modelProperties: {
id: {
serializedName: 'id',
type: {
name: 'String',
},
},
msGraphResourceId: {
serializedName: 'msGraphResourceId',
type: {
name: 'String',
},
},
scheduledStartTime: {
serializedName: 'scheduledStartTime',
type: {
name: 'DateTime',
},
},
scheduledEndTime: {
serializedName: 'scheduledEndTime',
type: {
name: 'DateTime',
},
},
joinUrl: {
serializedName: 'joinUrl',
type: {
name: 'String',
},
},
title: {
serializedName: 'title',
type: {
name: 'String',
},
},
type: {
serializedName: 'type',
type: {
name: 'String',
},
},
},
},
};

export const CardAction: msRest.CompositeMapper = {
serializedName: 'CardAction',
type: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ export {
Meeting,
TeamDetails,
TeamsChannelAccount,
TeamsMeetingDetails,
TeamsMeetingInfo,
TeamsMeetingParticipant,
} from './mappers';
61 changes: 60 additions & 1 deletion libraries/botframework-connector/src/teams/operations/teams.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable prettier/prettier */
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
Expand All @@ -8,7 +9,7 @@ import * as Models from '../models';
import * as Mappers from '../models/teamsMappers';
import * as Parameters from '../models/parameters';
import { TeamsConnectorClientContext } from '../';
import { ConversationList, TeamDetails, TeamsMeetingParticipant } from 'botframework-schema';
import { ConversationList, TeamDetails, TeamsMeetingInfo, TeamsMeetingParticipant } from 'botframework-schema';

/** Class representing a Teams. */
export class Teams {
Expand Down Expand Up @@ -181,6 +182,51 @@ export class Teams {
callback
) as Promise<Models.TeamsFetchMeetingParticipantResponse>;
}

/**
* Fetch meeting information.
*
* @summary Fetches information of a Teams meeting.
* @param meetingId Meeting Id, encoded as a BASE64 string.
* @param [options] The optional parameters
* @returns Promise<Models.TeamsFetchMeetingInfoResponse>
*/
fetchMeetingInfo(
meetingId: string,
options?: msRest.RequestOptionsBase | msRest.ServiceCallback<TeamDetails>
): Promise<Models.TeamsMeetingInfoResponse>;
/**
* @param meetingId Meeting Id, encoded as a BASE64 string.
* @param callback The callback
*/
fetchMeetingInfo(
meetingId: string,
callback: msRest.ServiceCallback<TeamsMeetingInfo>
): void;
/**
* @param meetingId Meeting Id, encoded as a BASE64 string.
* @param options The optional parameters
* @param callback The callback
*/
fetchMeetingInfo(
meetingId: string,
options: msRest.RequestOptionsBase | msRest.ServiceCallback<TeamDetails>,
callback: msRest.ServiceCallback<TeamsMeetingInfo>
): void;
fetchMeetingInfo(
meetingId: string,
options?: msRest.RequestOptionsBase | msRest.ServiceCallback<TeamDetails>,
callback?: msRest.ServiceCallback<TeamsMeetingInfo>
): Promise<Models.TeamsMeetingInfoResponse> {
return this.client.sendOperationRequest(
{
meetingId,
options,
},
fetchMeetingInfoOperationSpec,
callback
) as Promise<Models.TeamsMeetingInfoResponse>;
}
}

// Operation Specifications
Expand Down Expand Up @@ -224,3 +270,16 @@ const fetchMeetingParticipantOperationSpec: msRest.OperationSpec = {
},
serializer,
};

const fetchMeetingInfoOperationSpec: msRest.OperationSpec = {
httpMethod: 'GET',
path: 'v1/meetings/{meetingId}',
urlParameters: [Parameters.meetingId],
responses: {
200: {
bodyMapper: Mappers.TeamsMeetingInfo,
},
default: {},
},
serializer,
};
Loading

0 comments on commit ec197fe

Please sign in to comment.