Added VariableExpression (#217)

* Add VariableExpression

* Workflow Runner bug fixes
This commit is contained in:
Sipke Schoorstra 2019-12-25 12:10:39 +01:00 committed by GitHub
parent 8092d166a5
commit ebc2a4a89e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 100 additions and 41 deletions

View file

@ -33,7 +33,7 @@ namespace Elsa.Activities.Console.Activities
protected override async Task<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
{
if (input == null)
return Halt();
return Suspend();
var receivedInput = await input.ReadLineAsync();
return Execute(receivedInput);

View file

@ -85,7 +85,7 @@ namespace Elsa.Activities.Http.Activities
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
return Halt(true);
return Suspend(true);
}
protected override async Task<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken)

View file

@ -30,7 +30,7 @@ namespace Elsa.Activities.MassTransit.Activities
}
protected override bool OnCanExecute(ActivityExecutionContext context) => context.Input.Value?.GetType() == MessageType;
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => Halt(true);
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) => Suspend(true);
protected override Task<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
{

View file

@ -44,7 +44,7 @@ namespace Elsa.Activities.Timers.Activities
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext workflowContext)
{
return Halt();
return Suspend();
}
protected override async Task<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
@ -55,7 +55,7 @@ namespace Elsa.Activities.Timers.Activities
return Done();
}
return Halt();
return Suspend();
}
private async Task<bool> IsExpiredAsync(ActivityExecutionContext context, CancellationToken cancellationToken)

View file

@ -38,14 +38,14 @@ namespace Elsa.Activities.Timers.Activities
{
var isExpired = await IsExpiredAsync(context, cancellationToken);
return isExpired ? (IActivityExecutionResult)Done() : Halt();
return isExpired ? (IActivityExecutionResult)Done() : Suspend();
}
protected override async Task<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
{
var isExpired = await IsExpiredAsync(context, cancellationToken);
return isExpired ? (IActivityExecutionResult)Done() : Halt();
return isExpired ? (IActivityExecutionResult)Done() : Suspend();
}
private async Task<bool> IsExpiredAsync(ActivityExecutionContext workflowContext, CancellationToken cancellationToken)

View file

@ -42,7 +42,7 @@ namespace Elsa.Activities.Timers.Activities
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
return Halt();
return Suspend();
}
protected override async Task<IActivityExecutionResult> OnResumeAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
@ -53,7 +53,7 @@ namespace Elsa.Activities.Timers.Activities
return Done();
}
return Halt();
return Suspend();
}
private async Task<bool> IsExpiredAsync(ActivityExecutionContext context, CancellationToken cancellationToken)

View file

@ -35,13 +35,11 @@ namespace Elsa.Activities.Timers.HostedServices
{
try
{
using (var scope = serviceProvider.CreateScope())
{
var workflowInvoker = scope.ServiceProvider.GetRequiredService<IWorkflowRunner>();
await workflowInvoker.TriggerAsync(nameof(TimerEvent), Variables.Empty, stoppingToken);
await workflowInvoker.TriggerAsync(nameof(CronEvent), Variables.Empty, stoppingToken);
await workflowInvoker.TriggerAsync(nameof(InstantEvent), Variables.Empty, stoppingToken);
}
using var scope = serviceProvider.CreateScope();
var workflowInvoker = scope.ServiceProvider.GetRequiredService<IWorkflowRunner>();
await workflowInvoker.TriggerAsync(nameof(TimerEvent), cancellationToken: stoppingToken);
await workflowInvoker.TriggerAsync(nameof(CronEvent), cancellationToken:stoppingToken);
await workflowInvoker.TriggerAsync(nameof(InstantEvent), cancellationToken: stoppingToken);
}
catch (Exception ex)
{

View file

@ -35,7 +35,7 @@ namespace Elsa.Activities.UserTask.Activities
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
return Halt(true);
return Suspend(true);
}
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)

View file

@ -18,7 +18,7 @@ namespace Elsa.Activities.Workflows.Activities
public class Correlate : Activity
{
[ActivityProperty(Hint = "An expression that evaluates to the value to store as the correlation ID.")]
public IWorkflowExpression<string> ValueScriptExpression
public IWorkflowExpression<string> Value
{
get => GetState<IWorkflowExpression<string>>();
set => SetState(value);
@ -26,7 +26,7 @@ namespace Elsa.Activities.Workflows.Activities
protected override async Task<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
{
var value = await context.EvaluateAsync(ValueScriptExpression, cancellationToken);
var value = await context.EvaluateAsync(Value, cancellationToken);
context.WorkflowExecutionContext.Workflow.CorrelationId = value;
return Done();
}

View file

@ -32,7 +32,7 @@ namespace Elsa.Activities.Workflows.Activities
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
return Halt(true);
return Suspend(true);
}
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)

View file

@ -12,7 +12,7 @@ namespace Elsa.Services.Extensions
public static Task TriggerAsync(
this IWorkflowRunner workflowRunner,
string activityType,
Variables input,
Variable input,
CancellationToken cancellationToken = default)
{
return workflowRunner.TriggerAsync(activityType, input, cancellationToken: cancellationToken);

View file

@ -17,6 +17,8 @@ namespace Elsa.Services.Models
bool isDisabled = false,
string? name = default,
string? description = default,
bool isLatest = false,
bool isPublished = false,
IEnumerable<IActivity>? activities = default,
IEnumerable<Connection>? connections = default)
{
@ -24,6 +26,8 @@ namespace Elsa.Services.Models
Version = version;
IsSingleton = isSingleton;
IsDisabled = isDisabled;
IsLatest = isLatest;
IsPublished = isPublished;
Name = name;
Description = description;
Activities = activities?.ToList() ?? new List<IActivity>();

View file

@ -0,0 +1,24 @@
using System;
using Elsa.Services.Models;
namespace Elsa.Expressions
{
public class VariableExpression : WorkflowExpression
{
public static string ExpressionType => "Variable";
public VariableExpression(string variableName, Type returnType) : base(ExpressionType, returnType)
{
VariableName = variableName;
}
public string VariableName { get; }
}
public class VariableExpression<T> : VariableExpression, IWorkflowExpression<T>
{
public VariableExpression(string variableName) : base(variableName, typeof(T))
{
}
}
}

View file

@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Services.Models;
namespace Elsa.Expressions
{
public class VariableHandler : IWorkflowExpressionHandler
{
public string Type => VariableExpression.ExpressionType;
public Task<object> EvaluateAsync(
IWorkflowExpression expression,
ActivityExecutionContext context,
CancellationToken cancellationToken)
{
var variableExpression = (VariableExpression)expression;
var result = context.GetVariable(variableExpression.VariableName);
return Task.FromResult(result);
}
}
}

View file

@ -75,6 +75,7 @@ namespace Microsoft.Extensions.DependencyInjection
.TryAddProvider<ITokenFormatter, XmlTokenFormatter>(ServiceLifetime.Singleton)
.TryAddProvider<IWorkflowExpressionHandler, LiteralHandler>(ServiceLifetime.Singleton)
.TryAddProvider<IWorkflowExpressionHandler, CodeHandler>(ServiceLifetime.Singleton)
.TryAddProvider<IWorkflowExpressionHandler, VariableHandler>(ServiceLifetime.Singleton)
.AddTransient<IWorkflowFactory, WorkflowFactory>()
.AddScoped<IActivityInvoker, ActivityInvoker>()
.AddScoped<IWorkflowExpressionEvaluator, WorkflowExpressionEvaluator>()

View file

@ -40,8 +40,11 @@ namespace Elsa.Messages.Handlers
public async Task Handle(WorkflowCompleted notification, CancellationToken cancellationToken)
{
if (notification.Workflow.Blueprint.DeleteCompletedWorkflows)
await workflowInstanceStore.DeleteAsync(notification.Workflow.Id, cancellationToken);
var workflow = notification.Workflow;
var blueprint = workflow.Blueprint;
if (blueprint.DeleteCompletedWorkflows || blueprint.PersistenceBehavior == WorkflowPersistenceBehavior.Suspended)
await workflowInstanceStore.DeleteAsync(workflow.Id, cancellationToken);
}
private async Task SaveWorkflowAsync(Workflow workflow, CancellationToken cancellationToken)

View file

@ -51,7 +51,7 @@ namespace Elsa.Persistence.Memory
{
var query = workflowInstances.Values.AsQueryable();
query = query.Where(x => x.Status == WorkflowStatus.Running);
query = query.Where(x => x.Status == WorkflowStatus.Suspended);
if (!string.IsNullOrWhiteSpace(correlationId))
query = query.Where(x => x.CorrelationId == correlationId);

View file

@ -8,7 +8,7 @@ namespace Elsa.Services
{
public abstract class Activity : ActivityBase
{
protected SuspendWorkflowResult Halt(bool continueOnFirstPass = false) => new SuspendWorkflowResult(continueOnFirstPass);
protected SuspendWorkflowResult Suspend(bool continueOnFirstPass = false) => new SuspendWorkflowResult(continueOnFirstPass);
protected OutcomeResult Outcomes(IEnumerable<string> names) => new OutcomeResult(names);
protected OutcomeResult Outcome(string name) => Outcomes(new[] { name });
protected OutcomeResult Outcome(string name, object output) => Outcome(name, Variable.From(output));

View file

@ -36,12 +36,22 @@ namespace Elsa.Services
var workflowDefinition = workflowBuilder().Build<T>();
return CreateWorkflow(workflowDefinition, input, workflowInstance, correlationId);
}
public WorkflowBlueprint CreateWorkflowBlueprint(WorkflowDefinitionVersion definition)
{
var activities = CreateActivities(definition.Activities).ToList();
var connections = CreateConnections(definition.Connections, activities).ToList();
return new WorkflowBlueprint(definition.DefinitionId, definition.Version, definition.IsSingleton, definition.IsDisabled, definition.Name, definition.Description, activities, connections);
return new WorkflowBlueprint(
definition.DefinitionId,
definition.Version,
definition.IsSingleton,
definition.IsDisabled,
definition.Name,
definition.Description,
definition.IsLatest,
definition.IsPublished,
activities,
connections);
}
public Workflow CreateWorkflow(
@ -52,7 +62,7 @@ namespace Elsa.Services
{
if (blueprint.IsDisabled)
throw new InvalidOperationException("Cannot instantiate disabled workflow definitions.");
var id = idGenerator.Generate();
var workflow = new Workflow(
id,

View file

@ -318,15 +318,13 @@ namespace Elsa.Services
? await ExecuteActivityAsync(workflowExecutionContext, scheduledActivity, cancellationToken)
: await ResumeActivityAsync(workflowExecutionContext, scheduledActivity, cancellationToken);
workflowExecutionContext.IsFirstPass = false;
start = true;
await mediator.Publish(new ActivityExecuted(workflow, activity), cancellationToken);
if (result == null)
break;
await result.ExecuteAsync(this, workflowExecutionContext, cancellationToken);
if (result != null)
await result.ExecuteAsync(this, workflowExecutionContext, cancellationToken);
workflowExecutionContext.IsFirstPass = false;
start = true;
}
// Determine new workflow state.
@ -470,7 +468,7 @@ namespace Elsa.Services
{
var instances = await workflowInstanceStore.ListByStatusAsync(
definition.Item1.DefinitionId,
WorkflowStatus.Running,
WorkflowStatus.Suspended,
cancellationToken
);

View file

@ -135,7 +135,7 @@ namespace Elsa.WorkflowBuilders
var connections = CreateConnections(connectionDefinitions, activities);
var definitionId = !string.IsNullOrWhiteSpace(Id) ? Id : idGenerator.Generate();
return new WorkflowBlueprint(definitionId, Version, IsSingleton, IsDisabled, Name, Description, activities, connections);
return new WorkflowBlueprint(definitionId, Version, IsSingleton, IsDisabled, Name, Description, true, true, activities, connections);
}
private IEnumerable<Connection> CreateConnections(IEnumerable<ConnectionDefinition> connectionDefinitions, IEnumerable<IActivity> activities)

View file

@ -25,7 +25,7 @@ namespace Sample02
var workflowBuilderFactory = services.GetRequiredService<Func<IWorkflowBuilder>>();
var workflowBuilder = workflowBuilderFactory();
var workflowBlueprint = workflowBuilder
.StartWith<WriteLine>(x => x.Text = new LiteralExpression<string>("Hello world!"))
.StartWith<WriteLine>(x => x.Text = new CodeExpression<string>(() => "Hello world!"))
.Then(() => Console.WriteLine("Look, custom code!"))
.Then<WriteLine>(x => x.Text = new LiteralExpression<string>("Goodbye cruel world..."))
.Build();

View file

@ -16,7 +16,7 @@ namespace Sample05
.WithId("RecurringWorkflow")
.AsSingleton()
.StartWith<TimerEvent>(x => x.TimeoutScriptExpression = new LiteralExpression<TimeSpan>("00:00:05"))
.Then<WriteLine>(x => x.Text = new JavaScriptExpression<string>("`Trigger received. The time is: ${new Date().toISOString()}`"));
.Then<WriteLine>(x => x.Text = new CodeExpression<string>(context => $"Trigger received. The time is: {DateTime.UtcNow.ToLocalTime()}"));
}
}
}

View file

@ -37,7 +37,7 @@ namespace Sample08.Workflows
)
// Need to ensure that the correlation ID is the same string format that is used by the WorkflowConsumer<T>
// MassTransit will always use Guid values for correlation ID's, so need to ensure the same string format is used.
.Then<Correlate>(activity => activity.ValueScriptExpression = new JavaScriptExpression<string>("newGuid()"))
.Then<Correlate>(activity => activity.Value = new CodeExpression<string>(() => Guid.NewGuid().ToString()))
.Then<SendMassTransitMessage>(activity =>
{
activity.Message = new JavaScriptExpression<CreateOrder>("return { correlationId: correlationId(), order: order};");

View file

@ -8,7 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Sample13
{
/// <summary>
/// A strongly-typed workflows program demonstrating scripting, and branching.
/// A strongly-typed workflows program demonstrating scripting and branching.
/// </summary>
internal class Program
{