diff --git a/CHANGELOG.md b/CHANGELOG.md index c426e8f2c..5366099d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Update project to target .net 8.0 and .net 10 and upgrade dependencies by Tomer Rosenthal ([#510](https://github.com/microsoft/durabletask-dotnet/pull/510)) - Support worker features announcement by wangbill ([#502](https://github.com/microsoft/durabletask-dotnet/pull/502)) - Introduce custom copilot review instructions by halspang ([#503](https://github.com/microsoft/durabletask-dotnet/pull/503)) +- Add API to get orchestration history ([#516](https://github.com/microsoft/durabletask-dotnet/pull/516)) ## v1.17.1 - Fix Worker Registry and Add Documentation Notes by nytian in [#462](https://github.com/microsoft/durabletask-dotnet/pull/495) diff --git a/src/Client/Core/DurableTaskClient.cs b/src/Client/Core/DurableTaskClient.cs index f2d658d8f..5279e725b 100644 --- a/src/Client/Core/DurableTaskClient.cs +++ b/src/Client/Core/DurableTaskClient.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.ComponentModel; +using DurableTask.Core.History; using Microsoft.DurableTask.Client.Entities; using Microsoft.DurableTask.Internal; @@ -456,7 +457,8 @@ public virtual Task RestartAsync( /// Thrown if this implementation of does not /// support rewinding orchestrations. /// Thrown if the backend storage provider does not support rewinding orchestrations. - /// Thrown if an orchestration with the specified does not exist. + /// Thrown if an orchestration with the specified does not exist, + /// or if is the instance ID of an entity. /// Thrown if a precondition of the operation fails, for example if the specified /// orchestration is not in a "Failed" state. /// Thrown if the operation is canceled via the token. @@ -464,7 +466,25 @@ public virtual Task RewindInstanceAsync( string instanceId, string reason, CancellationToken cancellation = default) - => throw new NotSupportedException($"{this.GetType()} does not support orchestration rewind."); + => throw new NotSupportedException($"{this.GetType()} does not support orchestration rewind."); + + /// + /// Retrieves the history of the specified orchestration instance as a list of objects. + /// + /// The instance ID of the orchestration. + /// The cancellation token. + /// The list of objects representing the orchestration's history. + /// Thrown if this implementation of does not + /// support retrieving orchestration history. + /// Thrown if is null. + /// Thrown if an orchestration with the specified does not exist, + /// or if is the instance ID of an entity. + /// Thrown if the operation is canceled via the token. + /// Thrown if an internal error occurs when attempting to retrieve the orchestration history. + public virtual Task> GetOrchestrationHistoryAsync( + string instanceId, + CancellationToken cancellation = default) + => throw new NotSupportedException($"{this.GetType()} does not support retrieving orchestration history."); // TODO: Create task hub diff --git a/src/Client/Grpc/GrpcDurableTaskClient.cs b/src/Client/Grpc/GrpcDurableTaskClient.cs index 422f15060..766868926 100644 --- a/src/Client/Grpc/GrpcDurableTaskClient.cs +++ b/src/Client/Grpc/GrpcDurableTaskClient.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text; +using DurableTask.Core.History; using Google.Protobuf.WellKnownTypes; using Microsoft.DurableTask.Client.Entities; using Microsoft.DurableTask.Tracing; @@ -468,6 +469,49 @@ public override async Task RewindInstanceAsync( } } + /// + public override async Task> GetOrchestrationHistoryAsync( + string instanceId, + CancellationToken cancellation = default) + { + Check.NotNullOrEmpty(instanceId); + Check.NotEntity(this.options.EnableEntitySupport, instanceId); + + P.StreamInstanceHistoryRequest streamRequest = new() + { + InstanceId = instanceId, + ForWorkItemProcessing = false, + }; + + try + { + using AsyncServerStreamingCall streamResponse = + this.sidecarClient.StreamInstanceHistory(streamRequest, cancellationToken: cancellation); + + List pastEvents = []; + while (await streamResponse.ResponseStream.MoveNext(cancellation)) + { + pastEvents.AddRange(streamResponse.ResponseStream.Current.Events.Select(DurableTask.ProtoUtils.ConvertHistoryEvent)); + } + + return pastEvents; + } + catch (RpcException e) when (e.StatusCode == StatusCode.NotFound) + { + throw new ArgumentException($"An orchestration with the instanceId {instanceId} was not found.", e); + } + catch (RpcException e) when (e.StatusCode == StatusCode.Cancelled) + { + throw new OperationCanceledException( + $"The {nameof(this.GetOrchestrationHistoryAsync)} operation was canceled.", e, cancellation); + } + catch (RpcException e) when (e.StatusCode == StatusCode.Internal) + { + throw new InvalidOperationException( + $"An error occurred while retrieving the history for orchestration with instanceId {instanceId}.", e); + } + } + static AsyncDisposable GetCallInvoker(GrpcDurableTaskClientOptions options, out CallInvoker callInvoker) { if (options.Channel is GrpcChannel c) diff --git a/src/Shared/Grpc/ProtoUtils.cs b/src/Shared/Grpc/ProtoUtils.cs index f7036d28e..ab5551d45 100644 --- a/src/Shared/Grpc/ProtoUtils.cs +++ b/src/Shared/Grpc/ProtoUtils.cs @@ -80,7 +80,8 @@ internal static HistoryEvent ConvertHistoryEvent(P.HistoryEvent proto, EntityCon historyEvent = new ExecutionCompletedEvent( proto.EventId, proto.ExecutionCompleted.Result, - proto.ExecutionCompleted.OrchestrationStatus.ToCore()); + proto.ExecutionCompleted.OrchestrationStatus.ToCore(), + proto.ExecutionCompleted.FailureDetails.ToCore()); break; case P.HistoryEvent.EventTypeOneofCase.ExecutionTerminated: historyEvent = new ExecutionTerminatedEvent(proto.EventId, proto.ExecutionTerminated.Input);