Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add getMeetingInfo to TeamsInfo #3738

Merged
merged 4 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 'User.One@microsoft.com',
userPrincipalName: 'user1@microsoft.com',
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: 'User.One@microsoft.com',
userPrincipalName: 'user1@microsoft.com',
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 () {
mdrichardson marked this conversation as resolved.
Show resolved Hide resolved
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