Skip to content

Commit

Permalink
Add GetPagedFailedEntriesAsync method.
Browse files Browse the repository at this point in the history
  • Loading branch information
ceciliaavila authored Jul 18, 2023
1 parent c295eb4 commit c70cd2b
Show file tree
Hide file tree
Showing 6 changed files with 410 additions and 0 deletions.
16 changes: 16 additions & 0 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,22 @@ public static async Task<BatchOperationState> GetOperationStateAsync(ITurnContex
}
}

/// <summary>
/// Gets the failed entries of a batch operation.
/// </summary>
/// <param name="turnContext">The turn context.</param>
/// <param name="operationId">The operationId to get the failed entries of.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The list of failed entries of the operation.</returns>
public static async Task<BatchFailedEntriesResponse> GetPagedFailedEntriesAsync(ITurnContext turnContext, string operationId, CancellationToken cancellationToken = default)
{
operationId = operationId ?? throw new InvalidOperationException($"{nameof(operationId)} is required.");

using (var teamsClient = GetTeamsConnectorClient(turnContext))
{
return await teamsClient.Teams.GetPagedFailedEntriesAsync(operationId, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<IEnumerable<TeamsChannelAccount>> GetMembersAsync(IConnectorClient connectorClient, string conversationId, CancellationToken cancellationToken)
{
Expand Down
173 changes: 173 additions & 0 deletions libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,36 @@ public TeamsOperations(TeamsConnectorClient client)
return result;
}

/// <summary>
/// Gets the failed entries of a batch operation with error code and message.
/// </summary>
/// <param name="operationId">The operationId to get the failed entries of.</param>
/// <param name="customHeaders">Headers that will be added to request.</param>
/// <param name='cancellationToken'>The cancellation token.</param>
/// <exception cref="HttpOperationException">
/// Thrown when the operation returned an invalid status code.
/// </exception>
/// <exception cref="ValidationException">
/// Thrown when an input value does not match the expected data type, range or pattern.
/// </exception>
/// <returns>
/// A response object containing the state and responses of the operation.
/// </returns>
public async Task<HttpOperationResponse<BatchFailedEntriesResponse>> GetPagedFailedEntriesAsync(string operationId, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(operationId))
{
throw new ValidationException(ValidationRules.CannotBeNull, nameof(operationId));
}

// In case of throttling, it will retry the operation with default values (10 retries every 50 miliseconds).
var result = await RetryAction.RunAsync(
task: () => GetFailedEntriesPaginatedWithRetryAsync(operationId, customHeaders, cancellationToken),
retryExceptionHandler: (ex, ct) => HandleThrottlingException(ex, ct)).ConfigureAwait(false);

return result;
}

private static RetryParams HandleThrottlingException(Exception ex, int currentRetryCount)
{
if (ex is ThrottleException throttlException)
Expand Down Expand Up @@ -1484,6 +1514,149 @@ private static RetryParams HandleThrottlingException(Exception ex, int currentRe
return result;
}

private async Task<HttpOperationResponse<BatchFailedEntriesResponse>> GetFailedEntriesPaginatedWithRetryAsync(string operationId, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
// Tracing
var shouldTrace = ServiceClientTracing.IsEnabled;
string invocationId = null;
if (shouldTrace)
{
invocationId = ServiceClientTracing.NextInvocationId.ToString(CultureInfo.InvariantCulture);
var tracingParameters = new Dictionary<string, object>();
tracingParameters.Add("operationId", operationId);
tracingParameters.Add("cancellationToken", cancellationToken);
ServiceClientTracing.Enter(invocationId, this, "GetFailedEntriesPaginated", tracingParameters);
}

// Construct URL
var baseUrl = Client.BaseUri.AbsoluteUri;
var url = new Uri(new Uri(baseUrl + (baseUrl.EndsWith("/", StringComparison.InvariantCulture) ? string.Empty : "/")), "v3/batch/conversation/failedentries/{operationId}").ToString();
url = url.Replace("{operationId}", Uri.EscapeDataString(operationId));
using var httpRequest = new HttpRequestMessage();
httpRequest.Method = new HttpMethod("GET");
httpRequest.RequestUri = new Uri(url);

HttpResponseMessage httpResponse = null;

// Create HTTP transport objects
#pragma warning disable CA2000 // Dispose objects before losing scope
var result = new HttpOperationResponse<BatchFailedEntriesResponse>();
#pragma warning restore CA2000 // Dispose objects before losing scope
try
{
// Set Headers
if (customHeaders != null)
{
foreach (var header in customHeaders)
{
if (httpRequest.Headers.Contains(header.Key))
{
httpRequest.Headers.Remove(header.Key);
}

httpRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}

// Set Credentials
if (Client.Credentials != null)
{
cancellationToken.ThrowIfCancellationRequested();
await Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false);
}

// Send Request
if (shouldTrace)
{
ServiceClientTracing.SendRequest(invocationId, httpRequest);
}

cancellationToken.ThrowIfCancellationRequested();
httpResponse = await Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
if (shouldTrace)
{
ServiceClientTracing.ReceiveResponse(invocationId, httpResponse);
}

var statusCode = httpResponse.StatusCode;
cancellationToken.ThrowIfCancellationRequested();
string responseContent = null;

// Create Result
result.Request = httpRequest;
result.Response = httpResponse;

if ((int)statusCode == 200)
{
// 200: OK
responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
result.Body = Rest.Serialization.SafeJsonConvert.DeserializeObject<BatchFailedEntriesResponse>(responseContent, Client.DeserializationSettings);
}
catch (JsonException ex)
{
if (shouldTrace)
{
ServiceClientTracing.Error(invocationId, ex);
}

throw new SerializationException("Unable to deserialize the response.", responseContent, ex);
}
finally
{
// This means the request was successful. We can make our retry policy null.
if (currentRetryPolicy != null)
{
currentRetryPolicy = null;
}
}
}
else if ((int)statusCode == 429)
{
throw new ThrottleException() { RetryParams = currentRetryPolicy };
}
else
{
// 400: for requests with invalid operationId (Which should be of type GUID)

// invalid/unexpected status code
var ex = new HttpOperationException($"Operation returned an invalid status code '{statusCode}'");
if (httpResponse.Content != null)
{
responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
responseContent = string.Empty;
}

ex.Request = new HttpRequestMessageWrapper(httpRequest, operationId);
ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent);
if (shouldTrace)
{
ServiceClientTracing.Error(invocationId, ex);
}

throw ex;
}
}
finally
{
if (httpResponse != null)
{
httpResponse.Dispose();
}
}

if (shouldTrace)
{
ServiceClientTracing.Exit(invocationId, result);
}

return result;
}

private async Task<HttpOperationResponse<T>> GetResponseAsync<T>(string url, bool shouldTrace, string invocationId, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
// Create HTTP transport objects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,27 @@ public static partial class TeamsOperationsExtensions
throw new InvalidOperationException("TeamsOperations with GetOperationStateAsync is required for GetOperationStateAsync.");
}
}

/// <summary>
/// Gets the failed entries of a batch operation.
/// </summary>
/// <param name='operations'>The operations group for this extension method.</param>
/// <param name='operationId'>The operationId to get the failed entries of.</param>
/// <param name='cancellationToken'>The cancellation token.</param>
/// <returns>The list of failed entries of the operation.</returns>
public static async Task<BatchFailedEntriesResponse> GetPagedFailedEntriesAsync(this ITeamsOperations operations, string operationId, CancellationToken cancellationToken = default)
{
if (operations is TeamsOperations teamsOperations)
{
using (var result = await teamsOperations.GetPagedFailedEntriesAsync(operationId, null, cancellationToken).ConfigureAwait(false))
{
return result.Body;
}
}
else
{
throw new InvalidOperationException("TeamsOperations with GetPagedFailedEntriesAsync is required for GetPagedFailedEntriesAsync.");
}
}
}
}
36 changes: 36 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/BatchFailedEntriesResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.Teams
{
/// <summary>
/// Specifies the failed entries response.
/// Contains a list of <see cref="BatchFailedEntry"/>.
/// </summary>
public class BatchFailedEntriesResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="BatchFailedEntriesResponse"/> class.
/// </summary>
public BatchFailedEntriesResponse()
{
}

/// <summary>
/// Gets or sets the continuation token for paginated results.
/// </summary>
/// <value>The continuation token for paginated results.</value>
[JsonProperty(PropertyName = "continuationToken")]
public string ContinuationToken { get; set; }

/// <summary>
/// Gets the list of failed entries result of a batch operation.
/// </summary>
/// <value>The list of failed entries result of a batch operation.</value>
[JsonProperty(PropertyName = "failedEntries")]
public IList<BatchFailedEntry> FailedEntries { get; private set; } = new List<BatchFailedEntry>();
}
}
34 changes: 34 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/BatchFailedEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.Teams
{
/// <summary>
/// Specifies the failed entry with its id and error.
/// </summary>
public class BatchFailedEntry
{
/// <summary>
/// Initializes a new instance of the <see cref="BatchFailedEntry"/> class.
/// </summary>
public BatchFailedEntry()
{
}

/// <summary>
/// Gets or sets the id of the failed entry.
/// </summary>
/// <value>The id of the failed entry.</value>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }

/// <summary>
/// Gets or sets the error of the failed entry.
/// </summary>
/// <value>The error of the failed entry.</value>
[JsonProperty(PropertyName = "error")]
public string Error { get; set; }
}
}
Loading

0 comments on commit c70cd2b

Please sign in to comment.