Skip to content

Commit 2546f07

Browse files
authored
User metadata and workflow metadata support (#378)
Fixes #359
1 parent 66436bf commit 2546f07

24 files changed

+666
-67
lines changed

src/Temporalio/Client/Schedules/Schedule.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ public record Schedule(
3030
/// <param name="proto">Proto.</param>
3131
/// <param name="dataConverter">Data converter.</param>
3232
/// <returns>Converted value.</returns>
33-
internal static Schedule FromProto(
33+
internal static async Task<Schedule> FromProtoAsync(
3434
Api.Schedule.V1.Schedule proto, DataConverter dataConverter) =>
3535
new(
36-
Action: ScheduleAction.FromProto(proto.Action, dataConverter),
36+
Action: await ScheduleAction.FromProtoAsync(proto.Action, dataConverter).ConfigureAwait(false),
3737
Spec: ScheduleSpec.FromProto(proto.Spec))
3838
{
3939
Policy = SchedulePolicy.FromProto(proto.Policies),

src/Temporalio/Client/Schedules/ScheduleAction.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ public abstract record ScheduleAction
1616
/// <param name="proto">Proto.</param>
1717
/// <param name="dataConverter">Data converter.</param>
1818
/// <returns>Converted value.</returns>
19-
internal static ScheduleAction FromProto(
19+
internal static async Task<ScheduleAction> FromProtoAsync(
2020
Api.Schedule.V1.ScheduleAction proto, DataConverter dataConverter)
2121
{
2222
if (proto.StartWorkflow != null)
2323
{
24-
return ScheduleActionStartWorkflow.FromProto(proto.StartWorkflow, dataConverter);
24+
return await ScheduleActionStartWorkflow.FromProtoAsync(
25+
proto.StartWorkflow, dataConverter).ConfigureAwait(false);
2526
}
2627
else
2728
{

src/Temporalio/Client/Schedules/ScheduleActionStartWorkflow.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public static ScheduleActionStartWorkflow Create(
8484
/// <param name="proto">Proto.</param>
8585
/// <param name="dataConverter">Data converter.</param>
8686
/// <returns>Converted value.</returns>
87-
internal static ScheduleActionStartWorkflow FromProto(
87+
internal static async Task<ScheduleActionStartWorkflow> FromProtoAsync(
8888
Api.Workflow.V1.NewWorkflowExecutionInfo proto, DataConverter dataConverter)
8989
{
9090
IReadOnlyCollection<object?> args = proto.Input == null ?
@@ -93,6 +93,8 @@ internal static ScheduleActionStartWorkflow FromProto(
9393
var headers = proto.Header?.Fields?.ToDictionary(
9494
kvp => kvp.Key,
9595
kvp => (IEncodedRawValue)new EncodedRawValue(dataConverter, kvp.Value));
96+
var (staticSummary, staticDetails) =
97+
await dataConverter.FromUserMetadataAsync(proto.UserMetadata).ConfigureAwait(false);
9698
return new(
9799
Workflow: proto.WorkflowType.Name,
98100
Args: args,
@@ -109,6 +111,8 @@ internal static ScheduleActionStartWorkflow FromProto(
109111
TypedSearchAttributes = proto.SearchAttributes == null ?
110112
SearchAttributeCollection.Empty :
111113
SearchAttributeCollection.FromProto(proto.SearchAttributes),
114+
StaticSummary = staticSummary,
115+
StaticDetails = staticDetails,
112116
},
113117
Headers: headers);
114118
}
@@ -168,6 +172,8 @@ internal static ScheduleActionStartWorkflow FromProto(
168172
WorkflowTaskTimeout = Options.TaskTimeout is TimeSpan taskTimeout ?
169173
Duration.FromTimeSpan(taskTimeout) : null,
170174
RetryPolicy = Options.RetryPolicy?.ToProto(),
175+
UserMetadata = await dataConverter.ToUserMetadataAsync(
176+
Options.StaticSummary, Options.StaticDetails).ConfigureAwait(false),
171177
};
172178
if (Options.Memo != null && Options.Memo.Count > 0)
173179
{

src/Temporalio/Client/Schedules/ScheduleDescription.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading;
5+
using System.Threading.Tasks;
56
using Temporalio.Api.WorkflowService.V1;
67
using Temporalio.Common;
78
using Temporalio.Converters;
@@ -16,16 +17,11 @@ public class ScheduleDescription
1617
private readonly Lazy<IReadOnlyDictionary<string, IEncodedRawValue>> memo;
1718
private readonly Lazy<SearchAttributeCollection> searchAttributes;
1819

19-
/// <summary>
20-
/// Initializes a new instance of the <see cref="ScheduleDescription"/> class.
21-
/// </summary>
22-
/// <param name="id">Workflow ID.</param>
23-
/// <param name="rawDescription">Raw proto description.</param>
24-
/// <param name="dataConverter">Data converter.</param>
25-
internal ScheduleDescription(
26-
string id, DescribeScheduleResponse rawDescription, DataConverter dataConverter)
20+
private ScheduleDescription(
21+
string id, Schedule schedule, DescribeScheduleResponse rawDescription, DataConverter dataConverter)
2722
{
2823
Id = id;
24+
Schedule = schedule;
2925
RawDescription = rawDescription;
3026
// Search attribute conversion is cheap so it doesn't need to lock on publication. But
3127
// memo conversion may use remote codec so it should only ever be created once lazily.
@@ -39,7 +35,6 @@ internal ScheduleDescription(
3935
SearchAttributeCollection.Empty :
4036
SearchAttributeCollection.FromProto(rawDescription.SearchAttributes),
4137
LazyThreadSafetyMode.PublicationOnly);
42-
Schedule = Schedule.FromProto(rawDescription.Schedule, dataConverter);
4338
Info = ScheduleInfo.FromProto(rawDescription.Info);
4439
}
4540

@@ -77,5 +72,20 @@ internal ScheduleDescription(
7772
/// Gets the raw proto description.
7873
/// </summary>
7974
internal DescribeScheduleResponse RawDescription { get; private init; }
75+
76+
/// <summary>
77+
/// Convert from proto.
78+
/// </summary>
79+
/// <param name="id">ID.</param>
80+
/// <param name="rawDescription">Proto.</param>
81+
/// <param name="dataConverter">Converter.</param>
82+
/// <returns>Converted value.</returns>
83+
internal static async Task<ScheduleDescription> FromProtoAsync(
84+
string id, DescribeScheduleResponse rawDescription, DataConverter dataConverter) =>
85+
new(
86+
id,
87+
await Schedule.FromProtoAsync(rawDescription.Schedule, dataConverter).ConfigureAwait(false),
88+
rawDescription,
89+
dataConverter);
8090
}
8191
}

src/Temporalio/Client/TemporalClient.Schedules.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ public override async Task<ScheduleDescription> DescribeScheduleAsync(
136136
ScheduleId = input.Id,
137137
},
138138
DefaultRetryOptions(input.RpcOptions)).ConfigureAwait(false);
139-
return new(input.Id, desc, Client.Options.DataConverter);
139+
return await ScheduleDescription.FromProtoAsync(
140+
input.Id, desc, Client.Options.DataConverter).ConfigureAwait(false);
140141
}
141142

142143
/// <inheritdoc />

src/Temporalio/Client/TemporalClient.Workflow.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ public override async Task<WorkflowExecutionDescription> DescribeWorkflowAsync(
332332
},
333333
},
334334
DefaultRetryOptions(input.Options?.Rpc)).ConfigureAwait(false);
335-
return new(resp, Client.Options.DataConverter);
335+
return await WorkflowExecutionDescription.FromProtoAsync(
336+
resp, Client.Options.DataConverter).ConfigureAwait(false);
336337
}
337338

338339
/// <inheritdoc />
@@ -513,6 +514,9 @@ private async Task<WorkflowHandle<TWorkflow, TResult>> StartWorkflowInternalAsyn
513514
WorkflowIdConflictPolicy = input.Options.IdConflictPolicy,
514515
RetryPolicy = input.Options.RetryPolicy?.ToProto(),
515516
RequestEagerExecution = input.Options.RequestEagerStart,
517+
UserMetadata = await Client.Options.DataConverter.ToUserMetadataAsync(
518+
input.Options.StaticSummary, input.Options.StaticDetails).
519+
ConfigureAwait(false),
516520
};
517521
if (input.Args.Count > 0)
518522
{
@@ -614,6 +618,7 @@ private async Task<WorkflowHandle<TWorkflow, TResult>> StartWorkflowInternalAsyn
614618
WorkflowStartDelay = req.WorkflowStartDelay,
615619
SignalName = input.Options.StartSignal,
616620
WorkflowIdConflictPolicy = input.Options.IdConflictPolicy,
621+
UserMetadata = req.UserMetadata,
617622
};
618623
if (input.Options.StartSignalArgs != null && input.Options.StartSignalArgs.Count > 0)
619624
{
Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Threading.Tasks;
12
using Temporalio.Api.WorkflowService.V1;
23
using Temporalio.Converters;
34

@@ -8,18 +9,49 @@ namespace Temporalio.Client
89
/// </summary>
910
public class WorkflowExecutionDescription : WorkflowExecution
1011
{
12+
private WorkflowExecutionDescription(
13+
DescribeWorkflowExecutionResponse rawDescription,
14+
string? staticSummary,
15+
string? staticDetails,
16+
DataConverter dataConverter)
17+
: base(rawDescription.WorkflowExecutionInfo, dataConverter)
18+
{
19+
RawDescription = rawDescription;
20+
StaticSummary = staticSummary;
21+
StaticDetails = staticDetails;
22+
}
23+
1124
/// <summary>
12-
/// Initializes a new instance of the <see cref="WorkflowExecutionDescription"/> class.
25+
/// Gets the single-line fixed summary for this workflow execution that may appear in
26+
/// UI/CLI. This can be in single-line Temporal markdown format.
1327
/// </summary>
14-
/// <param name="rawDescription">Raw description response.</param>
15-
/// <param name="dataConverter">Data converter for memos.</param>
16-
internal WorkflowExecutionDescription(
17-
DescribeWorkflowExecutionResponse rawDescription, DataConverter dataConverter)
18-
: base(rawDescription.WorkflowExecutionInfo, dataConverter) => RawDescription = rawDescription;
28+
/// <remarks>WARNING: This setting is experimental.</remarks>
29+
public string? StaticSummary { get; private init; }
30+
31+
/// <summary>
32+
/// Gets the general fixed details for this workflow execution that may appear in UI/CLI.
33+
/// This can be in Temporal markdown format and can span multiple lines.
34+
/// </summary>
35+
/// <remarks>WARNING: This setting is experimental.</remarks>
36+
public string? StaticDetails { get; private init; }
1937

2038
/// <summary>
2139
/// Gets the raw proto info.
2240
/// </summary>
2341
internal DescribeWorkflowExecutionResponse RawDescription { get; private init; }
42+
43+
/// <summary>
44+
/// Convert from proto.
45+
/// </summary>
46+
/// <param name="rawDescription">Raw description.</param>
47+
/// <param name="dataConverter">Data converter.</param>
48+
/// <returns>Converted value.</returns>
49+
internal static async Task<WorkflowExecutionDescription> FromProtoAsync(
50+
DescribeWorkflowExecutionResponse rawDescription, DataConverter dataConverter)
51+
{
52+
var (staticSummary, staticDetails) = await dataConverter.FromUserMetadataAsync(
53+
rawDescription.ExecutionConfig?.UserMetadata).ConfigureAwait(false);
54+
return new(rawDescription, staticSummary, staticDetails, dataConverter);
55+
}
2456
}
2557
}

src/Temporalio/Client/WorkflowOptions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ public WorkflowOptions(string id, string taskQueue)
4141
/// </summary>
4242
public string? TaskQueue { get; set; }
4343

44+
/// <summary>
45+
/// Gets or sets a single-line fixed summary for this workflow execution that may appear in
46+
/// UI/CLI. This can be in single-line Temporal markdown format.
47+
/// </summary>
48+
/// <remarks>WARNING: This setting is experimental.</remarks>
49+
public string? StaticSummary { get; set; }
50+
51+
/// <summary>
52+
/// Gets or sets general fixed details for this workflow execution that may appear in
53+
/// UI/CLI. This can be in Temporal markdown format and can span multiple lines. This is a
54+
/// fixed value on the workflow that cannot be updated. For details that can be updated, use
55+
/// <see cref="Workflows.Workflow.CurrentDetails" /> within the workflow.
56+
/// </summary>
57+
/// <remarks>WARNING: This setting is experimental.</remarks>
58+
public string? StaticDetails { get; set; }
59+
4460
/// <summary>
4561
/// Gets or sets the total workflow execution timeout including retries and continue as new.
4662
/// </summary>

src/Temporalio/Converters/ConverterExtensions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using Temporalio.Api.Common.V1;
66
using Temporalio.Api.Failure.V1;
7+
using Temporalio.Api.Sdk.V1;
78

89
namespace Temporalio.Converters
910
{
@@ -195,5 +196,44 @@ public static T ToValue<T>(this IPayloadConverter converter, IRawValue rawValue)
195196
/// <returns>The converted value.</returns>
196197
public static RawValue ToRawValue(this IPayloadConverter converter, object? value) =>
197198
new(converter.ToPayload(value));
199+
200+
/// <summary>
201+
/// Create user metadata using this converter.
202+
/// </summary>
203+
/// <param name="converter">Converter.</param>
204+
/// <param name="summary">Summary.</param>
205+
/// <param name="details">Details.</param>
206+
/// <returns>Created metadata if any.</returns>
207+
internal static async Task<UserMetadata?> ToUserMetadataAsync(
208+
this DataConverter converter, string? summary, string? details)
209+
{
210+
if (summary == null && details == null)
211+
{
212+
return null;
213+
}
214+
var metadata = new UserMetadata();
215+
if (summary != null)
216+
{
217+
metadata.Summary = await converter.ToPayloadAsync(summary).ConfigureAwait(false);
218+
}
219+
if (details != null)
220+
{
221+
metadata.Details = await converter.ToPayloadAsync(details).ConfigureAwait(false);
222+
}
223+
return metadata;
224+
}
225+
226+
/// <summary>
227+
/// Extract summary and details from the given user metadata.
228+
/// </summary>
229+
/// <param name="converter">Converter.</param>
230+
/// <param name="metadata">Metadata.</param>
231+
/// <returns>Extracted summary and details if any.</returns>
232+
internal static async Task<(string? Summary, string? Details)> FromUserMetadataAsync(
233+
this DataConverter converter, UserMetadata? metadata) => (
234+
Summary: metadata?.Summary is { } s ?
235+
await converter.ToValueAsync<string>(s).ConfigureAwait(false) : null,
236+
Details: metadata?.Details is { } d ?
237+
await converter.ToValueAsync<string>(d).ConfigureAwait(false) : null);
198238
}
199239
}
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Threading;
3+
using Temporalio.Workflows;
34

45
namespace Temporalio.Worker.Interceptors
56
{
@@ -8,11 +9,23 @@ namespace Temporalio.Worker.Interceptors
89
/// </summary>
910
/// <param name="Delay">Delay duration.</param>
1011
/// <param name="CancellationToken">Optional cancellation token.</param>
12+
/// <param name="Summary">Summary for the delay.</param>
1113
/// <remarks>
1214
/// WARNING: This constructor may have required properties added. Do not rely on the exact
1315
/// constructor, only use "with" clauses.
1416
/// </remarks>
1517
public record DelayAsyncInput(
1618
TimeSpan Delay,
17-
CancellationToken? CancellationToken);
19+
CancellationToken? CancellationToken,
20+
string? Summary)
21+
{
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="DelayAsyncInput"/> class.
24+
/// </summary>
25+
/// <param name="options">Options.</param>
26+
internal DelayAsyncInput(DelayOptions options)
27+
: this(options.Delay, options.CancellationToken, options.Summary)
28+
{
29+
}
30+
}
1831
}

0 commit comments

Comments
 (0)