Implement Switch expression evaluation

This commit is contained in:
Sipke Schoorstra 2021-04-12 22:08:44 +02:00
parent b00eea3c6d
commit 094eccd572
6 changed files with 94 additions and 4 deletions

View file

@ -18,7 +18,7 @@ namespace Elsa.Activities.ControlFlow
DisplayName = "Switch",
Category = "Control Flow",
Description = "Evaluate multiple conditions and continue execution depending on the results.",
Outcomes = new[] { OutcomeNames.Done }
Outcomes = new[] { OutcomeNames.Default }
)]
public class Switch : Activity, INotificationHandler<ScopeEvicted>
{
@ -55,8 +55,9 @@ namespace Elsa.Activities.ControlFlow
}
var matches = Cases.Where(x => x.Condition).Select(x => x.Name).ToList();
var results = Mode == SwitchMode.MatchFirst ? matches.Any() ? new[] { matches.First() } : new string[0] : matches.ToArray();
var outcomes = results;
var hasAnyMatches = matches.Any();
var results = Mode == SwitchMode.MatchFirst ? hasAnyMatches ? new[] { matches.First() } : new string[0] : matches.ToArray();
var outcomes = hasAnyMatches ? results : new[] { OutcomeNames.Default };
return Outcomes(outcomes);
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Expressions;
using Elsa.Serialization;
using Elsa.Services.Models;
namespace Elsa.Activities.ControlFlow
{
public class SwitchHandler : IExpressionHandler
{
private readonly IContentSerializer _contentSerializer;
public string Syntax => "Switch";
public SwitchHandler(IContentSerializer contentSerializer)
{
_contentSerializer = contentSerializer;
}
public async Task<object?> EvaluateAsync(string expression, Type returnType, ActivityExecutionContext context, CancellationToken cancellationToken)
{
var caseModels = TryDeserializeExpression(expression);
var evaluatedCases = await EvaluateCasesAsync(caseModels, context, cancellationToken).ToListAsync(cancellationToken);
return evaluatedCases;
}
private async IAsyncEnumerable<SwitchCase> EvaluateCasesAsync(IEnumerable<SwitchCaseModel> caseModels, ActivityExecutionContext context, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var validCaseModels = caseModels.Where(x => x.Expressions != null && !string.IsNullOrWhiteSpace(x.Syntax) && x.Expressions.ContainsKey(x.Syntax)).ToList();
var evaluator = context.GetService<IExpressionEvaluator>();
foreach (var caseModel in validCaseModels)
{
var syntax = caseModel.Syntax!;
var expression = caseModel.Expressions![syntax];
var result = await evaluator.EvaluateAsync<bool>(expression, syntax, context, cancellationToken);
yield return new SwitchCase(caseModel.Name, result);
}
}
private IList<SwitchCaseModel> TryDeserializeExpression(string expression)
{
try
{
return _contentSerializer.Deserialize<IList<SwitchCaseModel>>(expression);
}
catch
{
return new List<SwitchCaseModel>();
}
}
public record SwitchCaseModel(string Name, IDictionary<string, string>? Expressions, string? Syntax);
}
}

View file

@ -1,5 +1,6 @@
using System;
using Elsa;
using Elsa.Activities.ControlFlow;
using Elsa.Activities.Signaling;
using Elsa.Activities.Signaling.Services;
using Elsa.Activities.Workflows;
@ -147,6 +148,7 @@ namespace Microsoft.Extensions.DependencyInjection
.TryAddProvider<IExpressionHandler, LiteralHandler>(ServiceLifetime.Singleton)
.TryAddProvider<IExpressionHandler, VariableHandler>(ServiceLifetime.Singleton)
.TryAddProvider<IExpressionHandler, JsonHandler>(ServiceLifetime.Singleton)
.TryAddProvider<IExpressionHandler, SwitchHandler>(ServiceLifetime.Singleton)
.AddScoped<IExpressionEvaluator, ExpressionEvaluator>();
// Workflow providers.

View file

@ -0,0 +1,26 @@
import {ElsaPlugin} from "../services/elsa-plugin";
import {eventBus} from '../services/event-bus';
import {ActivityDesignDisplayContext, EventTypes, SyntaxNames} from "../models";
import {h} from "@stencil/core";
import {parseJson} from "../utils/utils";
import {SwitchCase} from "../components/editors/properties/elsa-switch-cases-property/models";
export class SwitchPlugin implements ElsaPlugin {
constructor() {
eventBus.on(EventTypes.ActivityDesignDisplaying, this.onActivityDesignDisplaying);
}
onActivityDesignDisplaying(context: ActivityDesignDisplayContext) {
const activityModel = context.activityModel;
if (activityModel.type !== 'Switch')
return;
const props = activityModel.properties || [];
const syntax = 'Switch';
const casesProp = props.find(x => x.name == 'Cases') || { expressions: {'Switch': ''}, syntax: syntax };
const expression = casesProp.expressions[syntax];
const cases: Array<SwitchCase> = parseJson(expression) || [];
context.outcomes = [...cases.map(x => x.name), 'Default'];
}
}

View file

@ -8,6 +8,7 @@ import {DefaultDriversPlugin} from "../plugins/default-drivers-plugin";
import {ForkPlugin} from "../plugins/fork-plugin";
import {RunJavascriptPlugin} from "../plugins/run-javascript-plugin";
import {ActivityIconProviderPlugin} from "../plugins/activity-icon-provider-plugin";
import {SwitchPlugin} from "../plugins/switch-plugin";
export class PluginManager {
@ -19,6 +20,7 @@ export class PluginManager {
new ActivityIconProviderPlugin(),
new IfPlugin(),
new ForkPlugin(),
new SwitchPlugin(),
new HttpEndpointPlugin(),
new TimerPlugin(),
new WriteLinePlugin(),

View file

@ -19,7 +19,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddScoped<ITypeDefinitionProvider, EnumTypeDefinitionProvider>()
.AddScoped<ITypeDefinitionProvider, EnumerableTypeDefinitionProvider>()
.AddScoped<IJavaScriptService, JintJavaScriptEvaluator>()
.AddTransient<IConvertsJintEvaluationResult>(s => new JintEvaluationResultConverterFactory(s).GetConverter())
.AddTransient(s => new JintEvaluationResultConverterFactory(s).GetConverter())
.AddTransient<IConvertsEnumerableToObject>(s => new EnumerableResultConverter(default))
.TryAddProvider<IExpressionHandler, JavaScriptExpressionHandler>(ServiceLifetime.Scoped)
.AddNotificationHandlers(typeof(JavaScriptServiceCollectionExtensions));