Skip to content

Commit dff2576

Browse files
MaxBrooks114maxcompylsfmskywalker
authored
Generic activity name fix (#6698)
* Refactor ActivityRegistry to populate all activities in workflow editor page - Modify ListAll to return distinct activity descriptors. - Update RegisterAsync to call Add with correct parameters. - Refactor RefreshDescriptorsAsync for better collection usage. - Split Add method into two overloads for clarity. - Improve logging for replacing existing activity descriptors. * Add FuncExpressionValueConverter for JSON serialization Implemented FuncExpressionValueConverter to handle serialization and deserialization of Func<ExpressionExecutionContext, ValueTask<object>> types, ensuring delegates are not serialized and cannot be rehydrated from JSON. Updated multiple serializers including ApiSerializer, BookmarkPayloadSerializer, JsonActivitySerializer, JsonPayloadSerializer, JsonWorkflowStateSerializer, and SafeSerializer to utilize the new converter in their JSON serialization options. * fix broken studio when when viewing suspended workflows * fix for tests * Refactor WorkflowStateExtractor for readability and safety Improved code formatting and consistency in the WorkflowStateExtractor class. Updated logic in several methods to enhance handling of input items, activity execution contexts, and completion callbacks. Replaced `First` with `FirstOrDefault` for safer item retrieval and added null checks to prevent exceptions. These changes improve overall code readability, maintainability, and safety within the workflow execution context. * Enhance ActivityDescriber with new functionality This commit introduces several improvements to the `ActivityDescriber` class, including: - A new `GetFriendlyActivityName` method for better naming of activity types. - Updates to `DescribeActivityAsync` to use the friendly name for `typeName` and `displayName`. - Refactoring of `flowPorts` initialization for improved readability. - Simplification of `GetInputProperties` and `GetOutputProperties` methods. - Streamlined creation of `OutputDescriptor` and `InputDescriptor` in their respective methods. - Addition of `DescribeInputPropertiesAsync` and `DescribeOutputPropertiesAsync` for asynchronous property descriptions. --------- Co-authored-by: Max Brooks <Max@compyl.com> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
1 parent c83d42e commit dff2576

File tree

2 files changed

+60
-60
lines changed

2 files changed

+60
-60
lines changed

src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessed
2222
{
2323
var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
2424
var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
25-
var typeName = activityAttr?.Type ?? activityType.Name;
25+
var friendlyName = GetFriendlyActivityName(activityType);
26+
var typeName = activityAttr?.Type ?? friendlyName;
2627
var typeVersion = activityAttr?.Version ?? 1;
2728
var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
2829
var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
29-
var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title);
30+
var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasing.Title);
3031
var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
3132
var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespace(ns) ?? "Miscellaneous";
3233
var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
@@ -42,16 +43,19 @@ where typeof(IActivity).IsAssignableFrom(prop.PropertyType)
4243
Name = portAttr?.Name ?? prop.Name,
4344
DisplayName = portAttr?.DisplayName ?? portAttr?.Name ?? prop.Name,
4445
Type = PortType.Embedded,
45-
IsBrowsable = portAttr != null && (portBrowsableAttr == null || portBrowsableAttr.Browsable)
46+
IsBrowsable = portAttr != null && (portBrowsableAttr == null || portBrowsableAttr.Browsable),
4647
};
4748

4849
var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
49-
var flowPorts = flowNodeAttr?.Outcomes.Select(x => new Port
50-
{
51-
Type = PortType.Flow,
52-
Name = x,
53-
DisplayName = x
54-
}).ToDictionary(x => x.Name) ?? new Dictionary<string, Port>();
50+
var flowPorts =
51+
flowNodeAttr
52+
?.Outcomes.Select(x => new Port
53+
{
54+
Type = PortType.Flow,
55+
Name = x,
56+
DisplayName = x,
57+
})
58+
.ToDictionary(x => x.Name) ?? new Dictionary<string, Port>();
5559

5660
var allPorts = embeddedPorts.Concat(flowPorts.Values);
5761
var inputProperties = GetInputProperties(activityType).ToList();
@@ -90,7 +94,7 @@ where typeof(IActivity).IsAssignableFrom(prop.PropertyType)
9094
var activity = activityFactory.Create(activityType, context);
9195
activity.Type = fullTypeName;
9296
return activity;
93-
}
97+
},
9498
};
9599

96100
// If the activity has a default output, set its IsSerializable property to the value of the OutputAttribute.IsSerializable property.
@@ -106,12 +110,10 @@ where typeof(IActivity).IsAssignableFrom(prop.PropertyType)
106110
}
107111

108112
/// <inheritdoc />
109-
public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) =>
110-
activityType.GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute<InputAttribute>() != null).DistinctBy(x => x.Name);
113+
public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) => activityType.GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute<InputAttribute>() != null).DistinctBy(x => x.Name);
111114

112115
/// <inheritdoc />
113-
public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) =>
114-
activityType.GetProperties().Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).DistinctBy(x => x.Name).ToList();
116+
public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) => activityType.GetProperties().Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).DistinctBy(x => x.Name).ToList();
115117

116118
/// <inheritdoc />
117119
public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationToken = default)
@@ -121,18 +123,7 @@ public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyI
121123
var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
122124
var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
123125

124-
return Task.FromResult(new OutputDescriptor
125-
(
126-
(outputAttribute?.Name ?? propertyInfo.Name).Pascalize(),
127-
outputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
128-
wrappedPropertyType,
129-
propertyInfo.GetValue,
130-
propertyInfo.SetValue,
131-
propertyInfo,
132-
descriptionAttribute?.Description ?? outputAttribute?.Description,
133-
outputAttribute?.IsBrowsable ?? true,
134-
outputAttribute?.IsSerializable
135-
));
126+
return Task.FromResult(new OutputDescriptor((outputAttribute?.Name ?? propertyInfo.Name).Pascalize(), outputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), wrappedPropertyType, propertyInfo.GetValue, propertyInfo.SetValue, propertyInfo, descriptionAttribute?.Description ?? outputAttribute?.Description, outputAttribute?.IsBrowsable ?? true, outputAttribute?.IsSerializable));
136127
}
137128

138129
/// <inheritdoc />
@@ -150,8 +141,7 @@ public async Task<InputDescriptor> DescribeInputPropertyAsync(PropertyInfo prope
150141

151142
var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken);
152143

153-
return new InputDescriptor
154-
(
144+
return new InputDescriptor(
155145
inputAttribute?.Name ?? propertyInfo.Name,
156146
wrappedPropertyType,
157147
propertyInfo.GetValue,
@@ -214,6 +204,15 @@ public static string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAt
214204
return InputUIHints.SingleLine;
215205
}
216206

207+
private static string GetFriendlyActivityName(Type t)
208+
{
209+
if (!t.IsGenericType)
210+
return t.Name;
211+
var baseName = t.Name.Substring(0, t.Name.IndexOf('`'));
212+
var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
213+
return $"{baseName}<{argNames}>";
214+
}
215+
217216
private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, CancellationToken cancellationToken = default)
218217
{
219218
return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
@@ -223,4 +222,4 @@ private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(
223222
{
224223
return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)));
225224
}
226-
}
225+
}

src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public WorkflowState Extract(WorkflowExecutionContext workflowExecutionContext)
3131
IsSystem = workflowExecutionContext.Workflow.IsSystem,
3232
CreatedAt = workflowExecutionContext.CreatedAt,
3333
UpdatedAt = workflowExecutionContext.UpdatedAt,
34-
FinishedAt = workflowExecutionContext.FinishedAt
34+
FinishedAt = workflowExecutionContext.FinishedAt,
3535
};
3636

3737
ExtractProperties(state, workflowExecutionContext);
@@ -64,12 +64,13 @@ public async Task<WorkflowExecutionContext> ApplyAsync(WorkflowExecutionContext
6464
ApplyScheduledActivities(state, workflowExecutionContext);
6565
return workflowExecutionContext;
6666
}
67-
67+
6868
private void ApplyInput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
6969
{
7070
// Only add input from state if the input doesn't already exist on the workflow execution context.
7171
foreach (var inputItem in state.Input)
72-
if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key)) workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value);
72+
if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key))
73+
workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value);
7374
}
7475

7576
private IDictionary<string, object> GetPersistableInput(WorkflowExecutionContext workflowExecutionContext)
@@ -102,24 +103,23 @@ private void ApplyProperties(WorkflowState state, WorkflowExecutionContext workf
102103

103104
private static async Task ApplyActivityExecutionContextsAsync(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
104105
{
105-
var activityExecutionContexts = (await Task.WhenAll(
106-
state.ActivityExecutionContexts.Select(async item => await CreateActivityExecutionContextAsync(item))))
107-
.Where(x => x != null)
108-
.Select(x => x!)
109-
.ToList();
106+
var activityExecutionContexts = (await Task.WhenAll(state.ActivityExecutionContexts.Select(async item => await CreateActivityExecutionContextAsync(item)))).Where(x => x != null).Select(x => x!).ToList();
110107

111108
var lookup = activityExecutionContexts.ToDictionary(x => x.Id);
112109

113110
// Reconstruct hierarchy.
114111
foreach (var contextState in state.ActivityExecutionContexts.Where(x => !string.IsNullOrWhiteSpace(x.ParentContextId)))
115112
{
116-
var parentContext = lookup[contextState.ParentContextId!];
117-
var contextId = contextState.Id;
118-
119-
if (lookup.TryGetValue(contextId, out var context))
113+
if (lookup.ContainsKey(contextState.ParentContextId))
120114
{
121-
context.ExpressionExecutionContext.ParentContext = parentContext.ExpressionExecutionContext;
122-
context.ParentActivityExecutionContext = parentContext;
115+
var parentContext = lookup[contextState.ParentContextId!];
116+
var contextId = contextState.Id;
117+
118+
if (lookup.TryGetValue(contextId, out var context))
119+
{
120+
context.ExpressionExecutionContext.ParentContext = parentContext.ExpressionExecutionContext;
121+
context.ParentActivityExecutionContext = parentContext;
122+
}
123123
}
124124
}
125125

@@ -144,10 +144,10 @@ private static async Task ApplyActivityExecutionContextsAsync(WorkflowState stat
144144
var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity);
145145
activityExecutionContext.Id = activityExecutionContextState.Id;
146146
activityExecutionContext.Properties.Merge(properties);
147-
148-
if(activityExecutionContextState.ActivityState != null)
147+
148+
if (activityExecutionContextState.ActivityState != null)
149149
activityExecutionContext.ActivityState.Merge(activityExecutionContextState.ActivityState);
150-
150+
151151
activityExecutionContext.TransitionTo(activityExecutionContextState.Status);
152152
activityExecutionContext.IsExecuting = activityExecutionContextState.IsExecuting;
153153
activityExecutionContext.AggregateFaultCount = activityExecutionContextState.FaultCount;
@@ -164,16 +164,19 @@ private static void ApplyCompletionCallbacks(WorkflowState state, WorkflowExecut
164164
{
165165
foreach (var completionCallbackEntry in state.CompletionCallbacks)
166166
{
167-
var ownerActivityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.First(x => x.Id == completionCallbackEntry.OwnerInstanceId);
168-
var childNode = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildNodeId);
167+
var ownerActivityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == completionCallbackEntry.OwnerInstanceId);
168+
if (ownerActivityExecutionContext != null)
169+
{
170+
var childNode = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildNodeId);
169171

170-
if (childNode == null)
171-
continue;
172+
if (childNode == null)
173+
continue;
172174

173-
var callbackName = completionCallbackEntry.MethodName;
174-
var callbackDelegate = !string.IsNullOrEmpty(callbackName) ? ownerActivityExecutionContext.Activity.GetActivityCompletionCallback(callbackName) : default;
175-
var tag = completionCallbackEntry.Tag;
176-
workflowExecutionContext.AddCompletionCallback(ownerActivityExecutionContext, childNode, callbackDelegate, tag);
175+
var callbackName = completionCallbackEntry.MethodName;
176+
var callbackDelegate = !string.IsNullOrEmpty(callbackName) ? ownerActivityExecutionContext.Activity.GetActivityCompletionCallback(callbackName) : default;
177+
var tag = completionCallbackEntry.Tag;
178+
workflowExecutionContext.AddCompletionCallback(ownerActivityExecutionContext, childNode, callbackDelegate, tag);
179+
}
177180
}
178181
}
179182

@@ -208,9 +211,7 @@ private static void ExtractCompletionCallbacks(WorkflowState state, WorkflowExec
208211
throw new("Lost an owner context");
209212
}
210213

211-
var completionCallbacks = workflowExecutionContext
212-
.CompletionCallbacks
213-
.Select(x => new CompletionCallbackState(x.Owner.Id, x.Child.NodeId, x.CompletionCallback?.Method.Name, x.Tag));
214+
var completionCallbacks = workflowExecutionContext.CompletionCallbacks.Select(x => new CompletionCallbackState(x.Owner.Id, x.Child.NodeId, x.CompletionCallback?.Method.Name, x.Tag));
214215

215216
state.CompletionCallbacks = completionCallbacks.ToList();
216217
}
@@ -243,7 +244,7 @@ ActivityExecutionContextState CreateActivityExecutionContextState(ActivityExecut
243244
StartedAt = activityExecutionContext.StartedAt,
244245
CompletedAt = activityExecutionContext.CompletedAt,
245246
Tag = activityExecutionContext.Tag,
246-
DynamicVariables = activityExecutionContext.DynamicVariables
247+
DynamicVariables = activityExecutionContext.DynamicVariables,
247248
};
248249
return activityExecutionContextState;
249250
}
@@ -263,9 +264,9 @@ private void ExtractScheduledActivities(WorkflowState state, WorkflowExecutionCo
263264
Tag = x.Tag,
264265
Variables = x.Variables?.ToList(),
265266
ExistingActivityExecutionContextId = x.ExistingActivityExecutionContext?.Id,
266-
Input = x.Input
267+
Input = x.Input,
267268
});
268269

269270
state.ScheduledActivities = scheduledActivities.ToList();
270271
}
271-
}
272+
}

0 commit comments

Comments
 (0)