-
Notifications
You must be signed in to change notification settings - Fork 371
feat(workflow): align history propagation API with go-sdk #1825
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
base: master
Are you sure you want to change the base?
Changes from all commits
d1e40fe
b81c4ea
fcb1993
34a2c98
57c69da
b604220
36c2788
25fe1d7
6909ac2
da2f380
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
||
| /// <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> | ||
| /// <param name="entries"> | ||
| /// Workflow entries in execution order (ancestor first, immediate parent last). | ||
| /// </param> | ||
| public PropagatedHistory(IReadOnlyList<PropagatedHistoryEntry> entries) | ||
| { | ||
| _entries = entries ?? throw new ArgumentNullException(nameof(entries)); | ||
| } | ||
|
|
||
| /// <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() => _entries; | ||
|
|
||
| /// <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>(_entries.Count); | ||
| foreach (var entry in _entries) | ||
| { | ||
| if (seen.Add(entry.AppId)) | ||
| { | ||
| result.Add(entry.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 _entries | ||
| .Where(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase)) | ||
| .ToList(); | ||
|
Comment on lines
+80
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should definitely do this.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already done in 25fe1d7 — all name lookups switched to |
||
| } | ||
|
|
||
| /// <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 = _entries.Count - 1; i >= 0; i--) | ||
| { | ||
| if (string.Equals(_entries[i].Name, name, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| result = _entries[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 _entries | ||
| .Where(e => string.Equals(e.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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same idea as before - I don't know that this feature will always return only workflows. Today it might, but I'd like to leave our options open so we can avoid breaking changes. Please revert to "FilterByWorkflowName".
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pushing back: |
||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(workflowName); | ||
| return new PropagatedHistory( | ||
| _entries.Where(e => string.Equals(e.WorkflowName, workflowName, StringComparison.Ordinal)).ToList()); | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(instanceId); | ||
| return _entries | ||
| .Where(e => string.Equals(e.InstanceId, instanceId, StringComparison.Ordinal)) | ||
| .ToList(); | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I said in the comment,
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — renamed to |
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No interest in reflecting what type of event was recorded here? It's in the protos.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Captured via |
||
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No interest in reflecting what type of event was recorded here? It's in the protos.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in other classes, let's not introduce vague new terminology like "chunks" or "chains" in public-facing XML documentation when we already have terms that accurately and more obviously describe what they actually refer to. Such descriptions might be perfectly fine in internal implementations as they reflect usage from the gRPC protos themselves, but this is a public type and there should be nothing in here that doesn't have an analog directly to the publicly accessible Dapr runtime documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done — public XML scrubbed of "chunks" and "chains" across the abstractions.