Skip to content

Commit 48b574a

Browse files
j03y-nxxbzjoey-orbysssfmskywalkerCopilot
authored
Added logic to search for workflow-as-activities based on DefinitionId (not only versionId) (#7149)
* Inital commit: 1. Fixed problem: (re-)added logic to search for workflow-as-activities based on DefinitionId (not only versionId) 2. Added tests for Deserialiazation so this logic cannot disappear "unnoticed" in the future. * Found one flaw: workflowDefinitionId is also used in other activities. Therefore, we must make sure to only search by workflowDefinitionId when the value is a string (e.g. when workflow used as activity, the value will always be a constant string, because the workflowDefinitionId cannot be resolved using expressions) * Final attempt: 1) First try to find the activity by type name 2) Even if a descriptor is found by its type name, there might be multiple versions of a workflow-as-activity, hence; if the workflowDefinitionVersionId is specified, then this can override the initially found activity descriptor by type name. 3) Lastly, only when no activity descriptor is found by type name AND the activity JSON contains the property "workflowDeftinitionId", then we can search by workflowDefinitionId * Add `net8.0` and `net9.0` targets, update package versions for resilience libraries. * Exclude `net10.0` target framework from MySQL EF Core project due to Pomelo compatibility constraints. * Remove unnecessary whitespace in MySQL EF Core project file * Refactor `ActivityJsonConverterTests` to streamline registry setup and improve readability. * Adds null activity descriptor lookup mock Ensures the custom property lookup path is tested by mocking type name lookups to return null when searching for ActivityDescriptors. * Fix activity descriptor override assignment in `ActivityJsonConverter` Corrects the assignment logic for `activityDescriptor` and ensures `activityTypeVersion` uses the overridden descriptor's version when a custom property match is found. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor `ActivityJsonConverter` for readability and logic improvements - Adjust null-check handling for `activityDescriptor` overrides. - Improve comment clarity and consolidate lambda expressions. - Remove unnecessary whitespace. * Add blob extension handling to `BlobStorageWorkflowsProvider` - Introduce `BlobExtensions` for extracting blob file extensions. - Add `SupportsExtension` to `IBlobWorkflowFormatHandler` to filter handlers by supported extensions. * Remove unused workflow files and references in `Elsa.Server.Web` - Deleted `flowchart-test.elsa` and `multi-workflow-example.elsa`. - Removed corresponding references from the project file. --------- Co-authored-by: Joey Barten - Founder Orbyss <joey.barten@unfussiness.io> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4bbdd6f commit 48b574a

10 files changed

Lines changed: 410 additions & 67 deletions

File tree

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.11"/>
3131
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.11"/>
3232
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="9.0.11"/>
33-
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.0.11"/>
33+
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.10.0"/>
3434
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.11"/>
3535
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.11"/>
36-
<PackageVersion Include="Microsoft.Extensions.Resilience" Version="9.0.11"/>
36+
<PackageVersion Include="Microsoft.Extensions.Resilience" Version="9.10.0"/>
3737
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.11"/>
3838
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.11"/>
3939
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.11"/>

src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@
3737
<None Update="Workflows\for-loop.elsa">
3838
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
3939
</None>
40-
<None Update="Workflows\flowchart-test.elsa">
41-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
42-
</None>
4340
</ItemGroup>
4441

4542
</Project>

src/apps/Elsa.Server.Web/Workflows/flowchart-test.elsa

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/apps/Elsa.Server.Web/Workflows/multi-workflow-example.elsa

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
3+
<!-- Pomelo targets EF Core up to 9.x, so exclude MySQL provider for net10.0. -->
4+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
45
<Description>
56
Provides MySQL EF Core migrations for various modules.
67
</Description>
78
<PackageTags>elsa extension module persistence efcore mysql</PackageTags>
89
</PropertyGroup>
910

1011
<ItemGroup>
11-
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
12-
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
12+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all"/>
13+
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql"/>
1314
</ItemGroup>
1415

1516
<ItemGroup>
16-
<ProjectReference Include="..\Elsa.Persistence.EFCore\Elsa.Persistence.EFCore.csproj" />
17+
<ProjectReference Include="..\Elsa.Persistence.EFCore\Elsa.Persistence.EFCore.csproj"/>
1718
</ItemGroup>
1819

1920
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using FluentStorage.Blobs;
2+
3+
namespace Elsa.Extensions;
4+
5+
public static class BlobExtensions
6+
{
7+
public static string GetExtension(this Blob blob) => Path.GetExtension(blob.Name).TrimStart('.');
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Elsa.WorkflowProviders.BlobStorage.Contracts;
2+
3+
namespace Elsa.Extensions;
4+
5+
public static class BlobWorkflowFormatHandlerExtensions
6+
{
7+
public static bool SupportsExtension(this IBlobWorkflowFormatHandler handler, string extension) => handler.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
8+
}

src/modules/Elsa.WorkflowProviders.BlobStorage/Providers/BlobStorageWorkflowsProvider.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Elsa.WorkflowProviders.BlobStorage.Contracts;
1+
using Elsa.Extensions;
2+
using Elsa.WorkflowProviders.BlobStorage.Contracts;
23
using Elsa.Workflows.Runtime;
34
using FluentStorage.Blobs;
45
using JetBrains.Annotations;
@@ -71,11 +72,13 @@ public async ValueTask<IEnumerable<MaterializedWorkflow>> GetWorkflowsAsync(Canc
7172
{
7273
var blobStorage = _blobStorageProvider.GetBlobStorage();
7374
var content = await blobStorage.ReadTextAsync(blob.FullPath, cancellationToken: cancellationToken);
74-
7575
var contentType = blob.Properties.TryGetValue("ContentType", out var ct) ? ct?.ToString() : null;
7676

7777
foreach (var handler in _handlers)
7878
{
79+
if (!handler.SupportsExtension(blob.GetExtension()))
80+
continue;
81+
7982
if (!handler.CanHandle(blob, contentType))
8083
continue;
8184

src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,32 +81,53 @@ private string GetActivityDetails(JsonElement activityRoot, out int activityType
8181
activityDescriptor = null;
8282
activityTypeVersion = 0;
8383

84-
// First try and find the activity by its workflow definition version id. This is a special case when working with the WorkflowDefinitionActivity.
85-
if (activityRoot.TryGetProperty("workflowDefinitionVersionId", out var workflowDefinitionVersionIdElement))
86-
{
87-
var workflowDefinitionVersionId = workflowDefinitionVersionIdElement.GetString();
88-
activityDescriptor = activityRegistry.Find(x =>
89-
x.CustomProperties.TryGetValue("WorkflowDefinitionVersionId", out var value) && (string?)value == workflowDefinitionVersionId);
90-
activityTypeVersion = activityDescriptor?.Version ?? 0;
91-
}
84+
// First, we check whether the activity type name is a 'well-known' activity; not a workflow-as-activity
9285

9386
// If the activity type version is specified, use that to find the activity descriptor.
94-
if (activityDescriptor == null && activityRoot.TryGetProperty("version", out var activityVersionElement))
87+
if (activityRoot.TryGetProperty("version", out var activityVersionElement))
9588
{
9689
activityTypeVersion = activityVersionElement.GetInt32();
9790
activityDescriptor = activityRegistry.Find(activityTypeName, activityTypeVersion);
9891
}
9992

100-
// If the activity type version is not specified, use the latest version of the activity descriptor.
93+
// If a version is not specified, or activity with specified version is not found: use the latest version of the activity descriptor.
10194
if (activityDescriptor == null)
10295
{
10396
activityDescriptor = activityRegistry.Find(activityTypeName);
10497
activityTypeVersion = activityDescriptor?.Version ?? 0;
10598
}
10699

100+
// This is a special case when working with the WorkflowDefinitionActivity: workflowDefinitionVersionId should be used to find the workflow-as-activity.
101+
if (activityRoot.TryGetProperty("workflowDefinitionVersionId", out var workflowDefinitionVersionIdElement))
102+
{
103+
var activityDescriptorOverride = FindActivityDescriptorByCustomProperty("WorkflowDefinitionVersionId", workflowDefinitionVersionIdElement);
104+
if (activityDescriptorOverride is null)
105+
return activityTypeName;
106+
107+
activityDescriptor = activityDescriptorOverride;
108+
activityTypeVersion = activityDescriptor.Version;
109+
}
110+
// This is also a special case when working with the WorkflowDefinitionActivity: if no 'well-known' activity could be found, it might be a workflow-as-activity with a workflowDefinitionId
111+
else if (activityDescriptor is null
112+
&& activityRoot.TryGetProperty("workflowDefinitionId", out var workflowDefinitionIdElement)
113+
&& workflowDefinitionIdElement.ValueKind == JsonValueKind.String)
114+
{
115+
activityDescriptor = FindActivityDescriptorByCustomProperty("WorkflowDefinitionId", workflowDefinitionIdElement);
116+
activityTypeVersion = activityDescriptor?.Version ?? 0;
117+
}
118+
107119
return activityTypeName;
108120
}
109121

122+
private ActivityDescriptor? FindActivityDescriptorByCustomProperty(string customPropertyName, JsonElement valueElement)
123+
{
124+
if (valueElement.ValueKind != JsonValueKind.String)
125+
return null;
126+
127+
var searchValue = valueElement.GetString();
128+
return activityRegistry.Find(x => x.CustomProperties.TryGetValue(customPropertyName, out var value) && (string?)value == searchValue);
129+
}
130+
110131
private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
111132
{
112133
var clonedOptions = new JsonSerializerOptions(options);
@@ -115,7 +136,7 @@ private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
115136
clonedOptions.Converters.Add(new ExpressionJsonConverterFactory(expressionDescriptorRegistry));
116137
return clonedOptions;
117138
}
118-
139+
119140
private JsonSerializerOptions GetClonedWriterOptions(JsonSerializerOptions options)
120141
{
121142
var clonedOptions = GetClonedOptions(options);

0 commit comments

Comments
 (0)