Skip to content

Add hierarchical Teams API facade to TeamsBotApplication#334

Open
rido-min wants to merge 2 commits intonext/corefrom
next/core-api-clients
Open

Add hierarchical Teams API facade to TeamsBotApplication#334
rido-min wants to merge 2 commits intonext/corefrom
next/core-api-clients

Conversation

@rido-min
Copy link
Member

Introduced a structured API facade (TeamsBotApplication.Api) for Teams operations, grouping functionality into Conversations, Users, Teams, Meetings, and Batch sub-APIs. Added new classes for each sub-API, with extensive XML documentation. Updated TeamsBotApplication to expose the new facade. Added integration tests for the API hierarchy and error handling. Cleaned up .csproj by removing unused RunSettings property. This refactor improves discoverability, organization, and developer experience for Teams API usage.

This pull request introduces a new ActivitiesApi class to the codebase, which provides a unified interface for performing activity-related operations in conversations, such as sending, updating, deleting activities, handling conversation history, and retrieving activity members. The class wraps existing functionality from the ConversationClient, making these operations easier to use and more consistent throughout the application.

New API for activity operations:

  • Added the ActivitiesApi class in core/src/Microsoft.Teams.Bot.Apps/Api/ActivitiesApi.cs, providing methods for sending, updating, and deleting activities, uploading conversation history, and retrieving activity members.
  • Introduced overloads for delete and send history methods, allowing operations to be performed either by explicit IDs and URLs or by passing activity objects, improving flexibility and usability.

Integration and usability improvements:

  • All methods delegate to the underlying ConversationClient, ensuring consistency and reusability of existing logic.
  • Added support for optional custom headers and agentic identity parameters to enhance authentication and request customization.
  • Comprehensive XML documentation added to all methods, improving code clarity and maintainability.

Introduced a structured API facade (`TeamsBotApplication.Api`) for Teams operations, grouping functionality into Conversations, Users, Teams, Meetings, and Batch sub-APIs. Added new classes for each sub-API, with extensive XML documentation. Updated `TeamsBotApplication` to expose the new facade. Added integration tests for the API hierarchy and error handling. Cleaned up `.csproj` by removing unused RunSettings property. This refactor improves discoverability, organization, and developer experience for Teams API usage.
@rido-min rido-min marked this pull request as ready for review February 17, 2026 18:55
Added detailed tables to README.md mapping TeamsApi facade methods to their underlying REST endpoints. Documentation covers Conversations, Users (Token), Teams, Meetings, and Batch operations, including HTTP methods, endpoint paths, and relevant base URLs. Added notes on service URL usage and path parameter encoding.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a hierarchical Teams API facade surfaced as TeamsBotApplication.Api, organizing Teams operations into sub-APIs (Conversations/Activities/Members, Users/Token, Teams, Meetings, Batch) and adding integration tests to validate the facade structure and delegation.

Changes:

  • Added TeamsApi facade with sub-API classes for common Teams operations (activities, members, users/tokens, teams, meetings, batch).
  • Updated TeamsBotApplication to expose the facade via a new Api property.
  • Added integration tests covering API hierarchy and some error-handling behavior; removed an unused RunSettingsFilePath property from the test project.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
core/test/Microsoft.Teams.Bot.Core.Tests/TeamsApiFacadeTests.cs Adds integration tests for the new TeamsBotApplication.Api hierarchy and delegations.
core/test/Microsoft.Teams.Bot.Core.Tests/Microsoft.Teams.Bot.Core.Tests.csproj Removes unused RunSettingsFilePath property group.
core/src/Microsoft.Teams.Bot.Apps/TeamsBotApplication.cs Exposes new Api facade via lazy-initialized property.
core/src/Microsoft.Teams.Bot.Apps/Api/TeamsApi.cs Defines top-level hierarchical facade and wires up sub-APIs.
core/src/Microsoft.Teams.Bot.Apps/Api/ConversationsApi.cs Adds container for conversation-scoped APIs (Activities/Members).
core/src/Microsoft.Teams.Bot.Apps/Api/ActivitiesApi.cs Adds activity operations wrapper around ConversationClient.
core/src/Microsoft.Teams.Bot.Apps/Api/MembersApi.cs Adds conversation member operations wrapper around ConversationClient.
core/src/Microsoft.Teams.Bot.Apps/Api/UsersApi.cs Adds container for user-scoped APIs (Token).
core/src/Microsoft.Teams.Bot.Apps/Api/UserTokenApi.cs Adds user token operations wrapper around UserTokenClient.
core/src/Microsoft.Teams.Bot.Apps/Api/TeamsOperationsApi.cs Adds team/channel operations wrapper around TeamsApiClient.
core/src/Microsoft.Teams.Bot.Apps/Api/MeetingsApi.cs Adds meeting operations wrapper around TeamsApiClient.
core/src/Microsoft.Teams.Bot.Apps/Api/BatchApi.cs Adds batch messaging and batch-operation management wrapper around TeamsApiClient.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to +62
ArgumentNullException.ThrowIfNull(activity);
return _client.GetTokenAsync(
activity.From.Id!,
connectionName,
activity.ChannelId!,
code,
cancellationToken);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The activity-context overload uses null-forgiving operators for required fields (e.g., activity.From.Id and activity.ChannelId) but doesn’t validate them. If ChannelId/From.Id are missing, null can flow into UserTokenClient and result in confusing downstream failures or invalid requests. Add explicit validation (e.g., ThrowIfNullOrWhiteSpace on From.Id and ChannelId) for all activity-context methods in this API to fail fast with clear exceptions.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +112
ArgumentNullException.ThrowIfNull(activity);
return _client.FetchParticipantAsync(
meetingId,
participantId,
activity.ChannelData?.Tenant?.Id ?? throw new InvalidOperationException("Tenant ID not available in activity"),
activity.ServiceUrl!,
activity.From.GetAgenticIdentity(),
customHeaders,
cancellationToken);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throws InvalidOperationException when tenant ID is missing from the provided activity. Since this is invalid caller input (the argument doesn’t contain required data), prefer ArgumentException/ArgumentNullException (or ThrowIfNull/ThrowIfNullOrWhiteSpace) to keep exception semantics consistent with other argument validation across the clients.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +72
ArgumentNullException.ThrowIfNull(contextActivity);
return _client.SendMessageToListOfUsersAsync(
activity,
teamsMembers,
contextActivity.ChannelData?.Tenant?.Id ?? throw new InvalidOperationException("Tenant ID not available in activity"),
contextActivity.ServiceUrl!,
contextActivity.From.GetAgenticIdentity(),
customHeaders,
cancellationToken);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throws InvalidOperationException when tenant ID is missing from the provided activity. Because the tenant ID is required input derived from the argument, use an argument validation exception (ArgumentException/ArgumentNullException) for consistency and clearer caller feedback.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +51
public TeamsApi Api => _api ??= new TeamsApi(
ConversationClient,
UserTokenClient,
_teamsApiClient);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Api is lazily initialized on a singleton TeamsBotApplication without synchronization. Concurrent calls can create multiple TeamsApi instances and fail the “same instance” guarantee under load. Consider initializing in the constructor, using Lazy, or protecting the initialization with a lock/Interlocked to make it thread-safe.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rido-min copilot has a good point here, let's initialize this in the constructor?

@rido-min rido-min added the CORE label Feb 17, 2026
/// <param name="customHeaders">Optional custom headers to include in the request.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the response with the ID of the updated activity.</returns>
public Task<UpdateActivityResponse> UpdateAsync(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why we have disparity between send and update in extracting conv id/ activity id from activity itself? maybe we should have an overload for it similar to all other apis?

/// <param name="customHeaders">Optional custom headers to include in the request.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task DeleteAsync(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this overload for teamsactivity ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, having the overload defeats the benefits of polymorphism

/// <item><see cref="Members"/> - Member operations (get, delete)</item>
/// </list>
/// </remarks>
public class ConversationsApi
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there be a method here for create conversation? intentional omission?

/// <param name="customHeaders">Optional custom headers to include in the request.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the conversation member.</returns>
public Task<T> GetByIdAsync<T>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we combine GetByIdAsync<T and GetByIdAsync<ConversationAccount to GetByIdAsync <TeamsConversationAccount

/// <param name="customHeaders">Optional custom headers to include in the request.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the team details.</returns>
public Task<TeamDetails> GetByIdAsync(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should teamId also be extracted from activity here ? from activity.channelData.team.id

/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the list of channels.</returns>
public Task<ChannelList> GetChannelsAsync(
string teamId,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ same
Should teamId also be extracted from activity here ? from activity.channelData.team.id

/// <param name="customHeaders">Optional custom headers to include in the request.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task DeleteAsync(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, having the overload defeats the benefits of polymorphism

/// <param name="customHeaders">Optional custom headers to include in the request.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of members for the activity.</returns>
public Task<IList<ConversationAccount>> GetMembersAsync(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does "members of a specific activity" even mean? is it members of the conversation in which this activity was posted?

Comment on lines +48 to +51
public TeamsApi Api => _api ??= new TeamsApi(
ConversationClient,
UserTokenClient,
_teamsApiClient);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rido-min copilot has a good point here, let's initialize this in the constructor?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants