Skip to content
Closed
115 changes: 0 additions & 115 deletions src/Dapr.Workflow.Abstractions/HistoryEventKind.cs

This file was deleted.

121 changes: 85 additions & 36 deletions src/Dapr.Workflow.Abstractions/PropagatedHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,71 +15,120 @@ namespace Dapr.Workflow;

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

/// <summary>
/// Contains the workflow history that was propagated from ancestor workflow instances.
/// Each entry corresponds to a single ancestor's history.
/// Workflow history propagated from one or more ancestor workflows to a child workflow or activity.
/// </summary>
/// <remarks>
/// A workflow receives propagated history when it is scheduled with a
/// <see cref="HistoryPropagationScope"/> other than <see cref="HistoryPropagationScope.None"/>.
/// Use <see cref="WorkflowContext.GetPropagatedHistory"/> to retrieve the propagated history
/// inside a workflow implementation.
/// A propagated history is an ordered list of <see cref="PropagatedHistoryEntry"/> values,
/// one per ancestor workflow. Order is execution order: index 0 is the oldest ancestor,
/// the last entry is the immediate parent.
/// <para>
/// Use the <c>Get*</c> / <c>TryGet*</c> methods to walk the list by app, instance, or workflow name.
/// Mirrors the <c>PropagatedHistory</c> type in the Go and Python SDKs.
/// </para>
/// </remarks>
public sealed class PropagatedHistory
{
private readonly IReadOnlyList<PropagatedHistoryEntry> _entries;
private readonly IReadOnlyList<PropagatedHistoryEntry> _workflows;

/// <summary>
/// Initializes a new instance of <see cref="PropagatedHistory"/> with the given entries.
/// Initializes a new <see cref="PropagatedHistory"/> from the given workflow entries.
/// </summary>
/// <param name="entries">The propagated history entries from ancestor workflows.</param>
public PropagatedHistory(IReadOnlyList<PropagatedHistoryEntry> entries)
/// <param name="workflows">
/// Workflow entries in execution order (ancestor first, immediate parent last).
/// </param>
public PropagatedHistory(IReadOnlyList<PropagatedHistoryEntry> workflows)
{
_entries = entries ?? throw new ArgumentNullException(nameof(entries));
_workflows = workflows ?? throw new ArgumentNullException(nameof(workflows));
}

/// <summary>
/// Gets the ordered list of propagated history entries.
/// The first entry corresponds to the immediate parent workflow; subsequent entries
/// correspond to progressively older ancestors when <see cref="HistoryPropagationScope.Lineage"/> is used.
/// Returns every workflow entry in the propagated history, in execution order
/// (ancestor first, immediate parent last).
/// </summary>
public IReadOnlyList<PropagatedHistoryEntry> Entries => _entries;
public IReadOnlyList<PropagatedHistoryEntry> GetWorkflows() => _workflows;

/// <summary>
/// Returns a new <see cref="PropagatedHistory"/> containing only entries from the specified App ID.
/// Returns an ordered, deduplicated list of app IDs in this propagated history.
/// </summary>
/// <param name="appId">The Dapr App ID to filter by.</param>
/// <returns>A filtered <see cref="PropagatedHistory"/> instance.</returns>
public PropagatedHistory FilterByAppId(string appId)
public IReadOnlyList<string> GetAppIds()
{
ArgumentException.ThrowIfNullOrWhiteSpace(appId);
return new PropagatedHistory(
_entries.Where(e => string.Equals(e.AppId, appId, StringComparison.OrdinalIgnoreCase)).ToList());
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var result = new List<string>(_workflows.Count);
foreach (var workflow in _workflows)
{
if (seen.Add(workflow.AppId))
{
result.Add(workflow.AppId);
}
}

return result;
}

/// <summary>
/// Returns a new <see cref="PropagatedHistory"/> containing only the entry with the specified instance ID.
/// Returns every workflow entry whose name matches, in execution order. Useful when the
/// list contains the same name more than once (e.g. recursion or ContinueAsNew).
/// </summary>
/// <param name="instanceId">The workflow instance ID to filter by.</param>
/// <returns>A filtered <see cref="PropagatedHistory"/> instance.</returns>
public PropagatedHistory FilterByInstanceId(string instanceId)
/// <param name="name">The workflow name to filter by.</param>
/// <returns>An empty list when no match is found.</returns>
public IReadOnlyList<PropagatedHistoryEntry> GetWorkflowsByName(string name)
{
ArgumentException.ThrowIfNullOrWhiteSpace(instanceId);
return new PropagatedHistory(
_entries.Where(e => string.Equals(e.InstanceId, instanceId, StringComparison.Ordinal)).ToList());
ArgumentException.ThrowIfNullOrWhiteSpace(name);
return _workflows
.Where(w => string.Equals(w.Name, name, StringComparison.OrdinalIgnoreCase))
.ToList();
}

/// <summary>
/// Tries to return the most recent workflow entry whose name matches.
/// </summary>
/// <param name="name">The workflow name to look up.</param>
/// <param name="result">When this method returns <see langword="true"/>, the last matching workflow entry; otherwise <see langword="null"/>.</param>
/// <returns><see langword="true"/> if a matching entry was found; otherwise <see langword="false"/>.</returns>
public bool TryGetLastWorkflowByName(string name, [NotNullWhen(true)] out PropagatedHistoryEntry? result)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
for (var i = _workflows.Count - 1; i >= 0; i--)
{
if (string.Equals(_workflows[i].Name, name, StringComparison.OrdinalIgnoreCase))
{
result = _workflows[i];
return true;
}
}

result = null;
return false;
}

/// <summary>
/// Returns every workflow entry produced by the given app, in execution order.
/// </summary>
/// <param name="appId">The Dapr App ID to filter by.</param>
/// <returns>An empty list when no match is found.</returns>
public IReadOnlyList<PropagatedHistoryEntry> GetWorkflowsByAppId(string appId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(appId);
return _workflows
.Where(w => string.Equals(w.AppId, appId, StringComparison.OrdinalIgnoreCase))
.ToList();
}

/// <summary>
/// Returns a new <see cref="PropagatedHistory"/> containing only entries for the specified workflow name.
/// Returns every workflow entry produced by the given instance, in execution order.
/// Usually a single entry, except when the same instance reappears via ContinueAsNew.
/// </summary>
/// <param name="workflowName">The workflow name to filter by.</param>
/// <returns>A filtered <see cref="PropagatedHistory"/> instance.</returns>
public PropagatedHistory FilterByWorkflowName(string workflowName)
/// <param name="instanceId">The workflow instance ID to filter by.</param>
/// <returns>An empty list when no match is found.</returns>
public IReadOnlyList<PropagatedHistoryEntry> GetWorkflowsByInstanceId(string instanceId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(workflowName);
return new PropagatedHistory(
_entries.Where(e => string.Equals(e.WorkflowName, workflowName, StringComparison.Ordinal)).ToList());
ArgumentException.ThrowIfNullOrWhiteSpace(instanceId);
return _workflows
.Where(w => string.Equals(w.InstanceId, instanceId, StringComparison.Ordinal))
.ToList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow;

/// <summary>
/// A reconstructed view of a single activity invocation surfaced through propagated workflow history.
/// </summary>
/// <param name="Name">The scheduled name of the activity.</param>
/// <param name="Started">Whether the activity was scheduled in the propagated history.</param>
/// <param name="Completed">Whether the activity completed successfully.</param>
/// <param name="Failed">Whether the activity failed.</param>
/// <param name="Input">The JSON-encoded input payload, or <c>null</c> when unset.</param>
/// <param name="Output">The JSON-encoded output payload, or <c>null</c> when the activity has not completed.</param>
/// <param name="FailureDetails">The failure details when <paramref name="Failed"/> is true, otherwise <c>null</c>.</param>
/// <remarks>
/// Mirrors the <c>ActivityResult</c> type in the Go and Python SDKs so cross-language
/// quickstarts and audit patterns line up.
/// </remarks>
public sealed record PropagatedHistoryActivityResult(
string Name,
bool Started,
bool Completed,
bool Failed,
string? Input,
string? Output,
WorkflowTaskFailureDetails? FailureDetails);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Workflow;

/// <summary>
/// A reconstructed view of a single child workflow invocation surfaced through propagated workflow history.
/// </summary>
/// <param name="Name">The scheduled name of the child workflow.</param>
/// <param name="Started">Whether the child workflow was scheduled in the propagated history.</param>
/// <param name="Completed">Whether the child workflow completed successfully.</param>
/// <param name="Failed">Whether the child workflow failed.</param>
/// <param name="Output">The JSON-encoded output payload, or <c>null</c> when the child workflow has not completed.</param>
/// <param name="FailureDetails">The failure details when <paramref name="Failed"/> is true, otherwise <c>null</c>.</param>
/// <remarks>
/// Mirrors the <c>ChildWorkflowResult</c> type in the Go and Python SDKs.
/// </remarks>
public sealed record PropagatedHistoryChildWorkflowResult(
string Name,
bool Started,
bool Completed,
bool Failed,
string? Output,
WorkflowTaskFailureDetails? FailureDetails);
Loading