< Summary

Information
Class: Elsa.Workflows.Runtime.ActivityPropertyLogPersistenceEvaluator
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/LogPersistence/Services/ActivityPropertyLogPersistenceEvaluator.cs
Line coverage
93%
Covered lines: 106
Uncovered lines: 7
Coverable lines: 113
Total lines: 264
Line coverage: 93.8%
Branch coverage
75%
Covered branches: 36
Total branches: 48
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/LogPersistence/Services/ActivityPropertyLogPersistenceEvaluator.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Serialization;
 3using Elsa.Expressions.Contracts;
 4using Elsa.Expressions.Models;
 5using Elsa.Extensions;
 6using Elsa.Workflows.Activities;
 7using Elsa.Workflows.LogPersistence;
 8using Elsa.Workflows.LogPersistence.Strategies;
 9using Elsa.Workflows.Management.Options;
 10using Elsa.Workflows.Models;
 11using Elsa.Workflows.Serialization.Converters;
 12using Humanizer;
 13using Microsoft.Extensions.Logging;
 14using Microsoft.Extensions.Options;
 15
 16namespace Elsa.Workflows.Runtime;
 17
 18/* The following legacy JSON structure is expected to be found in the custom properties of the workflow and activity:
 19 * {
 20 *      "logPersistenceMode": {
 21 *          "default": "default",
 22 *          "inputs": { k : v },
 23 *          "outputs": { k: v }
 24 *          }
 25 *  }
 26 */
 27
 28/* The following JSON structure is expected to be found in the custom properties of the workflow and activity:
 29 * {
 30 *      "logPersistenceConfig": {
 31 *          "default": { "evaluationMode": "Strategy", "strategyType": "Elsa.Workflows.LogPersistence.Strategies.Inherit
 32 *          "inputs": { "input1" : { "evaluationMode": "Strategy", "strategyType": "Elsa.Workflows.LogPersistence.Strate
 33 *          "outputs": { "output1" : { "evaluationMode": "Strategy", "strategyType": "Elsa.Workflows.LogPersistence.Stra
 34 *          "internalState": { "evaluationMode": "Strategy", "strategyType": "Elsa.Workflows.LogPersistence.Strategies.I
 35 *          }
 36 *  }
 37 */
 38public class ActivityPropertyLogPersistenceEvaluator : IActivityPropertyLogPersistenceEvaluator
 39{
 40    private readonly IExpressionEvaluator _expressionEvaluator;
 41    private readonly JsonSerializerOptions _jsonOptions;
 42    private readonly IDictionary<string, ILogPersistenceStrategy> _strategies;
 43    private readonly IOptions<ManagementOptions> _options;
 44    private readonly ILogger _logger;
 45
 46    private const string LegacyKey = "logPersistenceMode";
 47    const string ConfigKey = "logPersistenceConfig";
 48
 74649    public ActivityPropertyLogPersistenceEvaluator(
 74650        ILogPersistenceStrategyService strategyService,
 74651        IExpressionDescriptorRegistry expressionDescriptorRegistry,
 74652        IExpressionEvaluator expressionEvaluator,
 74653        IOptions<ManagementOptions> options,
 74654        ILogger<ActivityPropertyLogPersistenceEvaluator> logger)
 55    {
 74656        _expressionEvaluator = expressionEvaluator;
 74657        _options = options;
 74658        _logger = logger;
 670059        _strategies = strategyService.ListStrategies().ToDictionary(x => x.GetType().GetSimpleAssemblyQualifiedName(), x
 74660        _jsonOptions = new JsonSerializerOptions
 74661        {
 74662            PropertyNameCaseInsensitive = true
 74663        }.WithConverters(
 74664            new ExpressionJsonConverterFactory(expressionDescriptorRegistry),
 74665            new JsonStringEnumConverter(),
 74666            new ExpandoObjectConverterFactory());
 74667    }
 68
 69    public async Task<ActivityLogPersistenceModeMap> EvaluateLogPersistenceModesAsync(ActivityExecutionContext context)
 70    {
 436471        var cancellationToken = context.CancellationToken;
 436472        var (legacyProps, configProps, defaultMode, workflow, rootContext) = await GetPersistenceDefaultsAsync(context, 
 436473        var map = new ActivityLogPersistenceModeMap();
 74
 436475        await EvaluatePropertiesAsync(context, "inputs", context.ActivityDescriptor.Inputs, legacyProps, configProps, de
 436476        await EvaluatePropertiesAsync(context, "outputs", context.ActivityDescriptor.Outputs, legacyProps, configProps, 
 436477        map.InternalState = await EvaluateInternalStateModeAsync(
 436478            context.ExpressionExecutionContext,
 436479            rootContext.ExpressionExecutionContext,
 436480            configProps,
 436481            workflow.CustomProperties,
 436482            defaultMode,
 436483            cancellationToken);
 84
 436485        return map;
 436486    }
 87
 88    public async Task<Dictionary<string, object>> GetPersistableOutputAsync(ActivityExecutionContext context)
 89    {
 390        var cancellationToken = context.WorkflowExecutionContext.CancellationToken;
 391        var (legacyProps, configProps, defaultMode, _, _) = await GetPersistenceDefaultsAsync(context, cancellationToken
 392        var outputs = context.GetOutputs();
 393        return await GetPersistablePropertiesAsync(context, outputs, "outputs", legacyProps, configProps, defaultMode, c
 394    }
 95
 96    private async Task<(IDictionary<string, object> legacyProps, IDictionary<string, object> configProps, LogPersistence
 97    {
 872198        var legacyProps = context.Activity.CustomProperties.GetValueOrDefault<IDictionary<string, object>>(LegacyKey, ()
 942099        var rootContext = context.WorkflowExecutionContext.ActivityExecutionContexts.First(x => x.ParentActivityExecutio
 12255100        var workflow = (Workflow?)context.GetAncestors().FirstOrDefault(x => x.Activity is Workflow)?.Activity ?? contex
 11411101        var workflowDefault = await GetDefaultPersistenceModeAsync(rootContext.ExpressionExecutionContext, workflow.Cust
 11405102        var activityDefault = await GetDefaultPersistenceModeAsync(context.ExpressionExecutionContext, context.Activity.
 8721103        var configProps = context.Activity.CustomProperties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, ()
 4367104        return (legacyProps, configProps, activityDefault, workflow, rootContext);
 4367105    }
 106
 107    private async Task EvaluatePropertiesAsync(
 108        ActivityExecutionContext context,
 109        string key,
 110        IEnumerable<PropertyDescriptor> descriptors,
 111        IDictionary<string, object> legacyConfig,
 112        IDictionary<string, object> currentConfig,
 113        LogPersistenceMode defaultMode,
 114        IDictionary<string, LogPersistenceMode> resultMap,
 115        CancellationToken cancellationToken)
 116    {
 17436117        var legacySection = legacyConfig.GetValueOrDefault(key, () => new Dictionary<string, object>())!;
 17450118        var currentSection = currentConfig.GetValueOrDefault(key, () => new Dictionary<string, object>())!;
 119
 35296120        foreach (var descriptor in descriptors)
 121        {
 8920122            resultMap[descriptor.Name] = await EvaluatePropertyModeAsync(context.ExpressionExecutionContext, descriptor,
 123        }
 8728124    }
 125
 126    private async Task<LogPersistenceMode> EvaluatePropertyModeAsync(
 127        ExpressionExecutionContext executionContext,
 128        PropertyDescriptor descriptor,
 129        IDictionary<string, object> legacySection,
 130        IDictionary<string, object> currentSection,
 131        LogPersistenceMode defaultMode,
 132        CancellationToken cancellationToken)
 133    {
 8920134        var key = descriptor.Name.Camelize();
 17840135        var configObject = currentSection.GetValueOrDefault(key, () => null);
 8920136        var config = ConvertToConfig(configObject);
 8920137        if (config != null)
 0138            return await EvaluateConfigAsync(config, executionContext, () => defaultMode, cancellationToken);
 139
 17826140        var mode = legacySection.GetValueOrDefault(key, () => defaultMode);
 16106141        return ResolveMode(mode, () => defaultMode);
 8920142    }
 143
 144    private async Task<LogPersistenceMode> EvaluateInternalStateModeAsync(
 145        ExpressionExecutionContext activityExecutionContext,
 146        ExpressionExecutionContext workflowExecutionContext,
 147        IDictionary<string, object> activityConfig,
 148        IDictionary<string, object> workflowProperties,
 149        LogPersistenceMode defaultMode,
 150        CancellationToken cancellationToken)
 151    {
 8715152        var workflowConfig = workflowProperties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, () => new Dict
 8721153        var workflowInternalState = await EvaluateInternalStateConfigAsync(workflowExecutionContext, workflowConfig, () 
 8721154        return await EvaluateInternalStateConfigAsync(activityExecutionContext, activityConfig, () => workflowInternalSt
 4364155    }
 156
 157    private async Task<LogPersistenceMode> EvaluateInternalStateConfigAsync(
 158        ExpressionExecutionContext executionContext,
 159        IDictionary<string, object> configProps,
 160        Func<LogPersistenceMode> defaultFactory,
 161        CancellationToken cancellationToken)
 162    {
 17442163        var configObject = configProps.GetValueOrDefault("internalState", () => null);
 8728164        var config = ConvertToConfig(configObject);
 8728165        if (config != null)
 14166            return await EvaluateConfigAsync(config, executionContext, defaultFactory, cancellationToken);
 167
 8714168        return ResolveMode(LogPersistenceMode.Inherit, defaultFactory);
 8728169    }
 170
 171    private async Task<Dictionary<string, object>> GetPersistablePropertiesAsync(
 172        ActivityExecutionContext context,
 173        IDictionary<string, object> state,
 174        string key,
 175        IDictionary<string, object> legacyConfig,
 176        IDictionary<string, object> currentConfig,
 177        LogPersistenceMode defaultMode,
 178        CancellationToken cancellationToken)
 179    {
 3180        var result = new Dictionary<string, object>();
 6181        var legacySection = legacyConfig.GetValueOrDefault(key, () => new Dictionary<string, object>());
 6182        var currentSection = currentConfig.GetValueOrDefault(key, () => new Dictionary<string, object>());
 183
 18184        foreach (var item in state)
 185        {
 6186            var propKey = item.Key.Camelize();
 12187            var configObject = currentSection!.GetValueOrDefault(propKey, () => null);
 6188            var config = ConvertToConfig(configObject);
 6189            var mode = config != null
 0190                ? await EvaluateConfigAsync(config, context.ExpressionExecutionContext, () => defaultMode, cancellationT
 12191                : legacySection!.GetValueOrDefault(propKey, () => defaultMode);
 192
 6193            if (mode == LogPersistenceMode.Include || (mode == LogPersistenceMode.Inherit && (defaultMode == LogPersiste
 6194                result.Add(item.Key, item.Value);
 6195        }
 196
 3197        return result;
 3198    }
 199
 200    private async Task<LogPersistenceMode> GetDefaultPersistenceModeAsync(
 201        ExpressionExecutionContext executionContext,
 202        IDictionary<string, object> properties,
 203        Func<LogPersistenceMode> defaultFactory,
 204        CancellationToken cancellationToken)
 205    {
 17443206        var legacyProps = properties.GetValueOrDefault<IDictionary<string, object>>(LegacyKey, () => new Dictionary<stri
 17442207        var configProps = properties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, () => new Dictionary<stri
 8734208        var defaultObj = configProps!.TryGetValue("default", out var val) ? val : null;
 8734209        if (defaultObj == null)
 210        {
 8710211            var legacyDefault = legacyProps!.GetValueOrDefault("default", defaultFactory);
 8710212            return legacyDefault == LogPersistenceMode.Inherit ? defaultFactory() : legacyDefault;
 213        }
 214
 24215        var config = ConvertToConfig(defaultObj);
 24216        return await EvaluateConfigAsync(config, executionContext, defaultFactory, cancellationToken);
 8734217    }
 218
 219    private async Task<LogPersistenceMode> EvaluateConfigAsync(
 220        LogPersistenceConfiguration? config,
 221        ExpressionExecutionContext executionContext,
 222        Func<LogPersistenceMode> defaultFactory,
 223        CancellationToken cancellationToken)
 224    {
 38225        if (config?.EvaluationMode == LogPersistenceEvaluationMode.Strategy)
 226        {
 37227            var strategyType = config.StrategyType ?? typeof(Inherit).GetSimpleAssemblyQualifiedName();
 37228            if (!_strategies.TryGetValue(strategyType, out var strategy))
 0229                return defaultFactory();
 230
 37231            var strategyContext = new LogPersistenceStrategyContext(cancellationToken);
 37232            var mode = await strategy.GetPersistenceModeAsync(strategyContext);
 37233            return ResolveMode(mode, defaultFactory);
 234        }
 235
 1236        if (config?.Expression == null)
 0237            return defaultFactory();
 238
 239        try
 240        {
 1241            var mode = await _expressionEvaluator.EvaluateAsync<LogPersistenceMode>(config.Expression, executionContext)
 1242            return ResolveMode(mode, defaultFactory);
 243        }
 0244        catch (Exception ex)
 245        {
 0246            _logger.LogWarning(ex, "Error evaluating log persistence expression");
 0247            return defaultFactory();
 248        }
 38249    }
 250
 251    private LogPersistenceMode ResolveMode(LogPersistenceMode mode, Func<LogPersistenceMode> defaultFactory)
 252    {
 17672253        var m = mode == LogPersistenceMode.Inherit ? defaultFactory() : mode;
 17672254        return m == LogPersistenceMode.Inherit ? LogPersistenceMode.Include : m;
 255    }
 256
 257    private LogPersistenceConfiguration? ConvertToConfig(object? value)
 258    {
 35318259        if (value == null) return null;
 39260        if (value is LogPersistenceConfiguration config) return config;
 37261        var json = JsonSerializer.Serialize(value);
 37262        return JsonSerializer.Deserialize<LogPersistenceConfiguration>(json, _jsonOptions);
 263    }
 264}