diff --git a/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/RefreshActivityRegistryHandler.cs b/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/RefreshActivityRegistryHandler.cs index 60a179f04..3a08f3298 100644 --- a/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/RefreshActivityRegistryHandler.cs +++ b/src/modules/Elsa.Workflows.Management/Activities/WorkflowDefinitionActivity/RefreshActivityRegistryHandler.cs @@ -15,7 +15,8 @@ public class RefreshActivityRegistryHandler : INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler + INotificationHandler, + INotificationHandler { private readonly IActivityRegistryPopulator _activityRegistryPopulator; @@ -39,5 +40,7 @@ public class RefreshActivityRegistryHandler : /// public async Task HandleAsync(WorkflowDefinitionsDeleted notification, CancellationToken cancellationToken) => await RefreshAsync(cancellationToken); + public async Task HandleAsync(WorkflowDefinitionCreated notification, CancellationToken cancellationToken) => await RefreshAsync(cancellationToken); + private async Task RefreshAsync(CancellationToken cancellationToken) => await _activityRegistryPopulator.PopulateRegistryAsync(typeof(WorkflowDefinitionActivityProvider), cancellationToken); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionCreated.cs b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionCreated.cs new file mode 100644 index 000000000..f7a40529c --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionCreated.cs @@ -0,0 +1,5 @@ +using Elsa.Mediator.Contracts; +using Elsa.Workflows.Management.Entities; + +namespace Elsa.Workflows.Management.Notifications; +public record WorkflowDefinitionCreated(WorkflowDefinition WorkflowDefinition) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs index f8ef7d49e..9d575108b 100644 --- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/ActivityJsonConverter.cs @@ -1,5 +1,7 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; +using Elsa.Common.Models; using Elsa.Expressions.Contracts; using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; @@ -7,6 +9,7 @@ using Elsa.Workflows.Core.Activities; using Elsa.Workflows.Core.Contracts; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Management.Contracts; using Humanizer; namespace Elsa.Workflows.Management.Serialization.Converters; @@ -18,18 +21,21 @@ public class ActivityJsonConverter : JsonConverter { private readonly IActivityRegistry _activityRegistry; private readonly IActivityFactory _activityFactory; + private readonly IWorkflowDefinitionStore _workflowDefinitionStore; private readonly IExpressionSyntaxRegistry _expressionSyntaxRegistry; private readonly IServiceProvider _serviceProvider; /// public ActivityJsonConverter( - IActivityRegistry activityRegistry, + IActivityRegistry activityRegistry, IActivityFactory activityFactory, + IWorkflowDefinitionStore workflowDefinitionStore, IExpressionSyntaxRegistry expressionSyntaxRegistry, IServiceProvider serviceProvider) { _activityRegistry = activityRegistry; _activityFactory = activityFactory; + _workflowDefinitionStore = workflowDefinitionStore; _expressionSyntaxRegistry = expressionSyntaxRegistry; _serviceProvider = serviceProvider; } @@ -45,7 +51,7 @@ public class ActivityJsonConverter : JsonConverter var activityTypeName = activityTypeNameElement.GetString()!; var activityTypeVersion = doc.RootElement.TryGetProperty("version", out var activityTypeVersionElement) ? activityTypeVersionElement.GetInt32() : 1; - var activityDescriptor = _activityRegistry.Find(activityTypeName, activityTypeVersion); + var activityDescriptor = _activityRegistry.Find(activityTypeName, activityTypeVersion) ?? CheckImportedWorkflowAsActivity(doc, ref activityTypeName, ref activityTypeVersion); var newOptions = new JsonSerializerOptions(options); newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider)); @@ -73,25 +79,25 @@ public class ActivityJsonConverter : JsonConverter var propertyName = inputName.Camelize(); var nakedType = inputDescriptor.Type; var wrappedType = typeof(Input<>).MakeGenericType(nakedType); - - if (doc.RootElement.TryGetProperty(propertyName, out var propertyElement) && propertyElement.ValueKind != JsonValueKind.Null && propertyElement.ValueKind != JsonValueKind.Undefined) - { - var isWrapped = propertyElement.ValueKind == JsonValueKind.Object && propertyElement.GetProperty("typeName").ValueKind != JsonValueKind.Undefined; - - if (isWrapped) - { - var json = propertyElement.ToString(); - var inputValue = JsonSerializer.Deserialize(json, wrappedType, newOptions); - activity.SyntheticProperties[inputName] = inputValue!; - } - else - { - activity.SyntheticProperties[inputName] = propertyElement.ConvertTo(inputDescriptor.Type)!; - } + if (!doc.RootElement.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == JsonValueKind.Null || propertyElement.ValueKind == JsonValueKind.Undefined) + continue; + + var isWrapped = propertyElement.ValueKind == JsonValueKind.Object && propertyElement.GetProperty("typeName").ValueKind != JsonValueKind.Undefined; + + if (isWrapped) + { + var json = propertyElement.ToString(); + var inputValue = JsonSerializer.Deserialize(json, wrappedType, newOptions); + + activity.SyntheticProperties[inputName] = inputValue!; + } + else + { + activity.SyntheticProperties[inputName] = propertyElement.ConvertTo(inputDescriptor.Type)!; } } - + // Reconstruct synthetic outputs. foreach (var outputDescriptor in activityDescriptor.Outputs.Where(x => x.IsSynthetic)) { @@ -100,20 +106,20 @@ public class ActivityJsonConverter : JsonConverter var nakedType = outputDescriptor.Type; var wrappedType = typeof(Output<>).MakeGenericType(nakedType); - if (!doc.RootElement.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == JsonValueKind.Null || propertyElement.ValueKind == JsonValueKind.Undefined) + if (!doc.RootElement.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == JsonValueKind.Null || propertyElement.ValueKind == JsonValueKind.Undefined) continue; - + var memoryReferenceElement = propertyElement.GetProperty("memoryReference"); - if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement)) + if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement)) continue; - + var variable = new Variable { Id = memoryReferenceIdElement.GetString()! }; variable.Name = variable.Id; - + var output = Activator.CreateInstance(wrappedType, variable)!; activity.SyntheticProperties[outputName] = output!; @@ -127,10 +133,10 @@ public class ActivityJsonConverter : JsonConverter { var activityDescriptor = _activityRegistry.Find(value.Type, value.Version)!; var newOptions = new JsonSerializerOptions(options); - + newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider)); newOptions.Converters.Add(new OutputJsonConverterFactory(_serviceProvider)); - + // Write to a JsonObject so that we can add additional information. var activityModel = JsonSerializer.SerializeToNode(value, value.GetType(), newOptions)!; var syntheticInputs = activityDescriptor.Inputs.Where(x => x.IsSynthetic).ToList(); @@ -141,73 +147,95 @@ public class ActivityJsonConverter : JsonConverter { var inputName = inputDescriptor.Name; var propertyName = inputName.Camelize(); + + if (!value.SyntheticProperties.TryGetValue(inputName, out var inputValue)) + continue; - if (value.SyntheticProperties.TryGetValue(inputName, out var inputValue)) + var input = (Input?)inputValue; + + if (input == null) { - var input = (Input?)inputValue; - - if (input == null) - { - activityModel[propertyName] = null; - continue; - } - - var expression = input.Expression; - var expressionType = expression.GetType(); - var inputType = input.Type; - var memoryReferenceId = input.MemoryBlockReference().Id; - var expressionSyntaxDescriptor = _expressionSyntaxRegistry.Find(x => x.Type == expressionType); - - if (expressionSyntaxDescriptor == null) - throw new Exception($"Syntax descriptor with expression type {expressionType} not found in registry"); - - var inputModel = new - { - TypeName = inputType, - Expression = expressionSyntaxDescriptor.CreateSerializableObject(new SerializableObjectConstructorContext(expression)), - MemoryReference = new - { - Id = memoryReferenceId - } - }; - - activityModel[propertyName] = JsonSerializer.SerializeToNode(inputModel, inputModel.GetType(), newOptions); + activityModel[propertyName] = null; + continue; } + + var expression = input.Expression; + var expressionType = expression.GetType(); + var inputType = input.Type; + var memoryReferenceId = input.MemoryBlockReference().Id; + var expressionSyntaxDescriptor = _expressionSyntaxRegistry.Find(x => x.Type == expressionType); + + if (expressionSyntaxDescriptor == null) + throw new Exception($"Syntax descriptor with expression type {expressionType} not found in registry"); + + var inputModel = new + { + TypeName = inputType, + Expression = expressionSyntaxDescriptor.CreateSerializableObject(new SerializableObjectConstructorContext(expression)), + MemoryReference = new + { + Id = memoryReferenceId + } + }; + + activityModel[propertyName] = JsonSerializer.SerializeToNode(inputModel, inputModel.GetType(), newOptions); } - + // Write synthetic outputs. foreach (var outputDescriptor in syntheticOutputs) { var outputName = outputDescriptor.Name; var propertyName = outputName.Camelize(); + + if (!value.SyntheticProperties.TryGetValue(outputName, out var outputValue)) + continue; - if (value.SyntheticProperties.TryGetValue(outputName, out var outputValue)) + var output = (Output?)outputValue; + + if (output == null) { - var output = (Output?)outputValue; - - if (output == null) - { - activityModel[propertyName] = null; - continue; - } - - var outputType = outputDescriptor.Type; - var memoryReferenceId = output.MemoryBlockReference().Id; - - var outputModel = new - { - TypeName = outputType, - MemoryReference = new - { - Id = memoryReferenceId - } - }; - - activityModel[propertyName] = JsonSerializer.SerializeToNode(outputModel, outputModel.GetType(), newOptions); + activityModel[propertyName] = null; + continue; } + + var outputType = outputDescriptor.Type; + var memoryReferenceId = output.MemoryBlockReference().Id; + + var outputModel = new + { + TypeName = outputType, + MemoryReference = new + { + Id = memoryReferenceId + } + }; + + activityModel[propertyName] = JsonSerializer.SerializeToNode(outputModel, outputModel.GetType(), newOptions); } - + // Send the model to the writer. JsonSerializer.Serialize(writer, activityModel, newOptions); } + + /// + /// If a referenced activity's descriptor isn't found, it could be because it is at a different version, e.g. due to importing a workflow as an activity. + /// + private ActivityDescriptor? CheckImportedWorkflowAsActivity(JsonDocument doc, ref string activityTypeName, ref int activityTypeVersion) + { + if (!doc.RootElement.TryGetProperty("workflowDefinitionId", out var workflowDefinitionId)) + return null; + + var workflowDefinition = _workflowDefinitionStore.FindAsync(new WorkflowDefinitionFilter + { + DefinitionId = workflowDefinitionId.GetString(), + VersionOptions = VersionOptions.LatestOrPublished + }).Result; + + if (workflowDefinition == null) + return null; + + activityTypeName = workflowDefinition.Name!; + activityTypeVersion = workflowDefinition.Version; + return _activityRegistry.Find(activityTypeName!, activityTypeVersion); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs index 38c080396..bb6510c94 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs @@ -163,6 +163,11 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher await _workflowDefinitionStore.SaveAsync(draft, cancellationToken); + if (lastVersion is null) + { + await _eventPublisher.PublishAsync(new WorkflowDefinitionCreated(definition), cancellationToken); + } + if (lastVersion is { IsPublished: true, IsLatest: true }) { lastVersion.IsLatest = false;