Implement Fault model
This commit is contained in:
parent
d7b41de7d3
commit
65e7009e56
|
|
@ -4,6 +4,7 @@ using Elsa.Common.Entities;
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Persistence.EntityFrameworkCore.Common;
|
||||
using Elsa.Persistence.EntityFrameworkCore.Extensions;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
|
|
@ -130,13 +131,13 @@ public class EFCoreWorkflowInstanceStore : IWorkflowInstanceStore
|
|||
{
|
||||
}
|
||||
|
||||
public WorkflowInstanceState(WorkflowState workflowState, WorkflowFault? fault)
|
||||
public WorkflowInstanceState(WorkflowState workflowState, WorkflowFaultState? fault)
|
||||
{
|
||||
WorkflowState = workflowState;
|
||||
Fault = fault;
|
||||
}
|
||||
|
||||
public WorkflowState WorkflowState { get; init; } = default!;
|
||||
public WorkflowFault? Fault { get; set; }
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ public class Response
|
|||
public WorkflowSubStatus SubStatus { get; set; }
|
||||
public string? CorrelationId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public WorkflowFault? Fault { get; set; }
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset? LastExecutedAt { get; set; }
|
||||
public DateTimeOffset? FinishedAt { get; set; }
|
||||
|
|
|
|||
32
src/modules/Elsa.Workflows.Core/Activities/Fault.cs
Normal file
32
src/modules/Elsa.Workflows.Core/Activities/Fault.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// Faults the workflow.
|
||||
/// </summary>
|
||||
[Activity("Elsa", "Primitives", "Faults the workflow.")]
|
||||
public class Fault : ActivityBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[JsonConstructor]
|
||||
public Fault([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message to include with the fault.
|
||||
/// </summary>
|
||||
[Input(Description = "The message to include with the fault.")]
|
||||
public Input<string?> Message { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
var message = Message.Get(context);
|
||||
throw new Exception(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,8 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
CorrelationId = workflowExecutionContext.CorrelationId,
|
||||
Status = workflowExecutionContext.Status,
|
||||
SubStatus = workflowExecutionContext.SubStatus,
|
||||
Bookmarks = workflowExecutionContext.Bookmarks
|
||||
Bookmarks = workflowExecutionContext.Bookmarks,
|
||||
Fault = SerializeFault(workflowExecutionContext.Fault)
|
||||
};
|
||||
|
||||
SerializeProperties(state, workflowExecutionContext);
|
||||
|
|
@ -52,39 +53,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
workflowExecutionContext.Properties = state.Properties.Properties;
|
||||
}
|
||||
|
||||
private void GetOutput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
foreach (var node in workflowExecutionContext.Nodes)
|
||||
GetOutput(state, node);
|
||||
}
|
||||
|
||||
private void GetOutput(WorkflowState state, ActivityNode activityNode)
|
||||
{
|
||||
var output = GetOutputFrom(activityNode);
|
||||
|
||||
if (output.Any())
|
||||
state.ActivityOutput.Add(activityNode.NodeId, output);
|
||||
}
|
||||
|
||||
private void SetOutput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
foreach (var entry in state.ActivityOutput)
|
||||
{
|
||||
var activityId = entry.Key;
|
||||
var node = workflowExecutionContext.FindNodeById(activityId);
|
||||
var activityType = node.Activity.GetType();
|
||||
|
||||
foreach (var outputEntry in entry.Value)
|
||||
{
|
||||
var propertyName = outputEntry.Key;
|
||||
var propertyValue = outputEntry.Value;
|
||||
var propertyInfo = activityType.GetProperty(propertyName, BindingFlags.Public)!;
|
||||
propertyInfo.SetValue(node, propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeserializeCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
private static void DeserializeCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
foreach (var completionCallbackEntry in state.CompletionCallbacks)
|
||||
{
|
||||
|
|
@ -96,7 +65,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
}
|
||||
}
|
||||
|
||||
private void SerializeCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
private static void SerializeCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
// Assert all referenced owner contexts exist.
|
||||
foreach (var completionCallback in workflowExecutionContext.CompletionCallbacks)
|
||||
|
|
@ -111,7 +80,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
state.CompletionCallbacks = completionCallbacks.ToList();
|
||||
}
|
||||
|
||||
private void SerializeActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
private static void SerializeActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
ActivityExecutionContextState CreateActivityExecutionContextState(ActivityExecutionContext activityExecutionContext)
|
||||
{
|
||||
|
|
@ -139,7 +108,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
state.ActivityExecutionContexts = workflowExecutionContext.ActivityExecutionContexts.Reverse().Select(CreateActivityExecutionContextState).ToList();
|
||||
}
|
||||
|
||||
private void DeserializeActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
private static void DeserializeActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
ActivityExecutionContext CreateActivityExecutionContext(ActivityExecutionContextState activityExecutionContextState)
|
||||
{
|
||||
|
|
@ -167,7 +136,13 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
|
||||
workflowExecutionContext.ActivityExecutionContexts = activityExecutionContexts;
|
||||
}
|
||||
|
||||
private static WorkflowFaultState? SerializeFault(WorkflowFault? fault)
|
||||
{
|
||||
if (fault == null)
|
||||
return null;
|
||||
|
||||
private Dictionary<string, object> GetOutputFrom(ActivityNode activityNode) =>
|
||||
activityNode.GetType().GetProperties(BindingFlags.Public).Where(x => x.GetCustomAttribute<OutputAttribute>() != null).ToDictionary(x => x.Name, x => x.GetValue(activityNode)!);
|
||||
var exceptionState = ExceptionState.FromException(fault.Exception);
|
||||
return new WorkflowFaultState(exceptionState, fault.Message, fault.FaultedActivityId);
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,11 @@ public class WorkflowExecutionContext
|
|||
/// </summary>
|
||||
public IDictionary<object, object> TransientProperties { get; set; } = new Dictionary<object, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores any fault that may have occurred during execution. Faulting a workflow will effectively suspend subsequent execution.
|
||||
/// </summary>
|
||||
public WorkflowFault? Fault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="ExecuteActivityDelegate"/> delegate to invoke when executing the next activity.
|
||||
/// </summary>
|
||||
|
|
|
|||
10
src/modules/Elsa.Workflows.Core/Models/WorkflowFaultState.cs
Normal file
10
src/modules/Elsa.Workflows.Core/Models/WorkflowFaultState.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Elsa.Workflows.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds information about a workflow fault.
|
||||
/// </summary>
|
||||
/// <param name="Exception">The exception that occurred</param>
|
||||
/// <param name="Message">A description about the fault. Usually the exception message, if there waa an exception.</param>
|
||||
/// <param name="FaultedActivityId">The ID of the activity that caused the workflow to fault.</param>
|
||||
public record WorkflowFault(Exception? Exception, string Message, string? FaultedActivityId);
|
||||
}
|
||||
32
src/modules/Elsa.Workflows.Core/State/ExceptionState.cs
Normal file
32
src/modules/Elsa.Workflows.Core/State/ExceptionState.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System.Collections;
|
||||
using System.Text.Json.Serialization;
|
||||
// ReSharper disable NotAccessedPositionalProperty.Global
|
||||
|
||||
namespace Elsa.Workflows.Core.State
|
||||
{
|
||||
/// <summary>
|
||||
/// A simplified, serializable model representing an exception.
|
||||
/// </summary>
|
||||
public record ExceptionState(Type Type, string Message, string? StackTrace, IDictionary Data, ExceptionState? InnerException = default)
|
||||
{
|
||||
// /// <summary>
|
||||
// /// Constructor
|
||||
// /// </summary>
|
||||
// [JsonConstructor]
|
||||
// public ExceptionState() : this(default!, default!, default, default!, default)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ExceptionState"/> from the specified exception.
|
||||
/// </summary>
|
||||
public static ExceptionState? FromException(Exception? ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return null;
|
||||
|
||||
return new ExceptionState(ex.GetType(), ex.Message, ex.StackTrace, ex.Data, FromException(ex.InnerException));
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/modules/Elsa.Workflows.Core/State/WorkflowFaultState.cs
Normal file
10
src/modules/Elsa.Workflows.Core/State/WorkflowFaultState.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Elsa.Workflows.Core.State
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds information about a workflow fault.
|
||||
/// </summary>
|
||||
/// <param name="Exception">The exception that occurred</param>
|
||||
/// <param name="Message">A description about the fault. Usually the exception message, if there waa an exception.</param>
|
||||
/// <param name="FaultedActivityId">The ID of the activity that caused the workflow to fault.</param>
|
||||
public record WorkflowFaultState(ExceptionState? Exception, string Message, string? FaultedActivityId);
|
||||
}
|
||||
|
|
@ -43,12 +43,12 @@ public class WorkflowState
|
|||
/// Collected bookmarks.
|
||||
/// </summary>
|
||||
public ICollection<Bookmark> Bookmarks { get; set; } = new List<Bookmark>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary of activity output.
|
||||
/// The serialized workflow state, if any.
|
||||
/// </summary>
|
||||
public IDictionary<string, IDictionary<string, object>> ActivityOutput { get; set; } = new Dictionary<string, IDictionary<string, object>>();
|
||||
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of callbacks that activities registered in order to be notified when the activities they scheduled complete.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class WorkflowInstance : Entity
|
|||
public WorkflowSubStatus SubStatus { get; set; }
|
||||
public string? CorrelationId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public WorkflowFault? Fault { get; set; }
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset? LastExecutedAt { get; set; }
|
||||
public DateTimeOffset? FinishedAt { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace Elsa.Workflows.Management.Models
|
||||
{
|
||||
public class SimpleException
|
||||
{
|
||||
public SimpleException(Type type, string message, string? stackTrace, IDictionary data, SimpleException? innerException = default)
|
||||
{
|
||||
Type = type;
|
||||
Message = message;
|
||||
StackTrace = stackTrace;
|
||||
InnerException = innerException;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public Type Type { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string? StackTrace { get; set; }
|
||||
public SimpleException? InnerException { get; set; }
|
||||
public IDictionary Data { get; set; }
|
||||
|
||||
public static SimpleException? FromException(Exception? ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return null;
|
||||
|
||||
var exceptionType = ex.GetType();
|
||||
var simpleException = new SimpleException(exceptionType, ex.Message, ex.StackTrace, ex.Data);
|
||||
|
||||
if (ex.InnerException != null)
|
||||
simpleException.InnerException = FromException(ex.InnerException);
|
||||
|
||||
return simpleException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
namespace Elsa.Workflows.Management.Models
|
||||
{
|
||||
public class SimpleExceptionProperty
|
||||
{
|
||||
public SimpleExceptionProperty(string name, object value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
namespace Elsa.Workflows.Management.Models
|
||||
{
|
||||
public record WorkflowFault(SimpleException? Exception, string Message, string? FaultedActivityId, object? ActivityInput, bool Resuming);
|
||||
}
|
||||
Loading…
Reference in a new issue