< 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
89%
Covered lines: 91
Uncovered lines: 11
Coverable lines: 102
Total lines: 243
Line coverage: 89.2%
Branch coverage
67%
Covered branches: 31
Total branches: 46
Branch coverage: 67.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
EvaluateLogPersistenceModesAsync()100%11100%
GetPersistableOutputAsync()100%11100%
GetPersistenceDefaultsAsync()100%44100%
EvaluatePropertiesAsync()100%22100%
EvaluatePropertyModeAsync()50%22100%
EvaluateInternalStateModeAsync()50%2266.66%
GetPersistablePropertiesAsync()62.5%1616100%
GetDefaultPersistenceModeAsync()100%66100%
EvaluateConfigAsync()41.66%341246.66%
ResolveMode(...)100%44100%
ConvertToConfig(...)75%44100%

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
 47049    public ActivityPropertyLogPersistenceEvaluator(
 47050        ILogPersistenceStrategyService strategyService,
 47051        IExpressionDescriptorRegistry expressionDescriptorRegistry,
 47052        IExpressionEvaluator expressionEvaluator,
 47053        IOptions<ManagementOptions> options,
 47054        ILogger<ActivityPropertyLogPersistenceEvaluator> logger)
 55    {
 47056        _expressionEvaluator = expressionEvaluator;
 47057        _options = options;
 47058        _logger = logger;
 423059        _strategies = strategyService.ListStrategies().ToDictionary(x => x.GetType().GetSimpleAssemblyQualifiedName(), x
 47060        _jsonOptions = new JsonSerializerOptions
 47061        {
 47062            PropertyNameCaseInsensitive = true
 47063        }.WithConverters(
 47064            new ExpressionJsonConverterFactory(expressionDescriptorRegistry),
 47065            new JsonStringEnumConverter(),
 47066            new ExpandoObjectConverterFactory());
 47067    }
 68
 69    public async Task<ActivityLogPersistenceModeMap> EvaluateLogPersistenceModesAsync(ActivityExecutionContext context)
 70    {
 375171        var cancellationToken = context.CancellationToken;
 375172        var (legacyProps, configProps, defaultMode) = await GetPersistenceDefaultsAsync(context, cancellationToken);
 375173        var map = new ActivityLogPersistenceModeMap();
 74
 375175        await EvaluatePropertiesAsync(context, "inputs", context.ActivityDescriptor.Inputs, legacyProps, configProps, de
 375176        await EvaluatePropertiesAsync(context, "outputs", context.ActivityDescriptor.Outputs, legacyProps, configProps, 
 375177        map.InternalState = await EvaluateInternalStateModeAsync(context.ExpressionExecutionContext, context.Activity.Cu
 78
 375179        return map;
 375180    }
 81
 82    public async Task<Dictionary<string, object>> GetPersistableOutputAsync(ActivityExecutionContext context)
 83    {
 384        var cancellationToken = context.WorkflowExecutionContext.CancellationToken;
 385        var (legacyProps, configProps, defaultMode) = await GetPersistenceDefaultsAsync(context, cancellationToken);
 386        var outputs = context.GetOutputs();
 387        return await GetPersistablePropertiesAsync(context, outputs, "outputs", legacyProps, configProps, defaultMode, c
 388    }
 89
 90    private async Task<(IDictionary<string, object> legacyProps, IDictionary<string, object> configProps, LogPersistence
 91    {
 749892        var legacyProps = context.Activity.CustomProperties.GetValueOrDefault<IDictionary<string, object>>(LegacyKey, ()
 810693        var rootContext = context.WorkflowExecutionContext.ActivityExecutionContexts.First(x => x.ParentActivityExecutio
 1080794        var workflow = (Workflow?)context.GetAncestors().FirstOrDefault(x => x.Activity is Workflow)?.Activity ?? contex
 994195        var workflowDefault = await GetDefaultPersistenceModeAsync(rootContext.ExpressionExecutionContext, workflow.Cust
 994096        var activityDefault = await GetDefaultPersistenceModeAsync(context.ExpressionExecutionContext, context.Activity.
 750697        var configProps = context.Activity.CustomProperties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, ()
 375498        return (legacyProps, configProps, activityDefault);
 375499    }
 100
 101    private async Task EvaluatePropertiesAsync(
 102        ActivityExecutionContext context,
 103        string key,
 104        IEnumerable<PropertyDescriptor> descriptors,
 105        IDictionary<string, object> legacyConfig,
 106        IDictionary<string, object> currentConfig,
 107        LogPersistenceMode defaultMode,
 108        IDictionary<string, LogPersistenceMode> resultMap,
 109        CancellationToken cancellationToken)
 110    {
 14988111        var legacySection = legacyConfig.GetValueOrDefault(key, () => new Dictionary<string, object>())!;
 15004112        var currentSection = currentConfig.GetValueOrDefault(key, () => new Dictionary<string, object>())!;
 113
 29538114        foreach (var descriptor in descriptors)
 115        {
 7267116            resultMap[descriptor.Name] = await EvaluatePropertyModeAsync(context.ExpressionExecutionContext, descriptor,
 117        }
 7502118    }
 119
 120    private async Task<LogPersistenceMode> EvaluatePropertyModeAsync(
 121        ExpressionExecutionContext executionContext,
 122        PropertyDescriptor descriptor,
 123        IDictionary<string, object> legacySection,
 124        IDictionary<string, object> currentSection,
 125        LogPersistenceMode defaultMode,
 126        CancellationToken cancellationToken)
 127    {
 7267128        var key = descriptor.Name.Camelize();
 14534129        var configObject = currentSection.GetValueOrDefault(key, () => null);
 7267130        var config = ConvertToConfig(configObject);
 7267131        if (config != null)
 0132            return await EvaluateConfigAsync(config, executionContext, () => defaultMode, cancellationToken);
 133
 14524134        var mode = legacySection.GetValueOrDefault(key, () => defaultMode);
 13201135        return ResolveMode(mode, () => defaultMode);
 7267136    }
 137
 138    private async Task<LogPersistenceMode> EvaluateInternalStateModeAsync(
 139        ExpressionExecutionContext executionContext,
 140        IDictionary<string, object> currentConfig,
 141        LogPersistenceMode defaultMode,
 142        CancellationToken cancellationToken)
 143    {
 7502144        var configObject = currentConfig.GetValueOrDefault("internalState", () => new Dictionary<string, object>())!;
 3751145        var config = ConvertToConfig(configObject);
 11253146        if (config != null) return await EvaluateConfigAsync(config, executionContext, () => defaultMode, cancellationTo
 0147        return LogPersistenceMode.Inherit;
 3751148    }
 149
 150    private async Task<Dictionary<string, object>> GetPersistablePropertiesAsync(
 151        ActivityExecutionContext context,
 152        IDictionary<string, object> state,
 153        string key,
 154        IDictionary<string, object> legacyConfig,
 155        IDictionary<string, object> currentConfig,
 156        LogPersistenceMode defaultMode,
 157        CancellationToken cancellationToken)
 158    {
 3159        var result = new Dictionary<string, object>();
 6160        var legacySection = legacyConfig.GetValueOrDefault(key, () => new Dictionary<string, object>());
 6161        var currentSection = currentConfig.GetValueOrDefault(key, () => new Dictionary<string, object>());
 162
 18163        foreach (var item in state)
 164        {
 6165            var propKey = item.Key.Camelize();
 12166            var configObject = currentSection!.GetValueOrDefault(propKey, () => null);
 6167            var config = ConvertToConfig(configObject);
 6168            var mode = config != null
 0169                ? await EvaluateConfigAsync(config, context.ExpressionExecutionContext, () => defaultMode, cancellationT
 12170                : legacySection!.GetValueOrDefault(propKey, () => defaultMode);
 171
 6172            if (mode == LogPersistenceMode.Include || (mode == LogPersistenceMode.Inherit && (defaultMode == LogPersiste
 6173                result.Add(item.Key, item.Value);
 6174        }
 175
 3176        return result;
 3177    }
 178
 179    private async Task<LogPersistenceMode> GetDefaultPersistenceModeAsync(
 180        ExpressionExecutionContext executionContext,
 181        IDictionary<string, object> properties,
 182        Func<LogPersistenceMode> defaultFactory,
 183        CancellationToken cancellationToken)
 184    {
 14997185        var legacyProps = properties.GetValueOrDefault<IDictionary<string, object>>(LegacyKey, () => new Dictionary<stri
 15008186        var configProps = properties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, () => new Dictionary<stri
 7508187        var defaultObj = configProps!.TryGetValue("default", out var val) ? val : null;
 7508188        if (defaultObj == null)
 189        {
 7500190            var legacyDefault = legacyProps!.GetValueOrDefault("default", defaultFactory);
 7500191            return legacyDefault == LogPersistenceMode.Inherit ? defaultFactory() : legacyDefault;
 192        }
 193
 8194        var config = ConvertToConfig(defaultObj);
 8195        return await EvaluateConfigAsync(config, executionContext, defaultFactory, cancellationToken);
 7508196    }
 197
 198    private async Task<LogPersistenceMode> EvaluateConfigAsync(
 199        LogPersistenceConfiguration? config,
 200        ExpressionExecutionContext executionContext,
 201        Func<LogPersistenceMode> defaultFactory,
 202        CancellationToken cancellationToken)
 203    {
 3759204        if (config?.EvaluationMode == LogPersistenceEvaluationMode.Strategy)
 205        {
 3759206            var strategyType = config.StrategyType ?? typeof(Inherit).GetSimpleAssemblyQualifiedName();
 3759207            if (!_strategies.TryGetValue(strategyType, out var strategy))
 0208                return defaultFactory();
 209
 3759210            var strategyContext = new LogPersistenceStrategyContext(cancellationToken);
 3759211            var mode = await strategy.GetPersistenceModeAsync(strategyContext);
 3759212            return ResolveMode(mode, defaultFactory);
 213        }
 214
 0215        if (config?.Expression == null)
 0216            return defaultFactory();
 217
 218        try
 219        {
 0220            var mode = await _expressionEvaluator.EvaluateAsync<LogPersistenceMode>(config.Expression, executionContext)
 0221            return ResolveMode(mode, defaultFactory);
 222        }
 0223        catch (Exception ex)
 224        {
 0225            _logger.LogWarning(ex, "Error evaluating log persistence expression");
 0226            return defaultFactory();
 227        }
 3759228    }
 229
 230    private LogPersistenceMode ResolveMode(LogPersistenceMode mode, Func<LogPersistenceMode> defaultFactory)
 231    {
 11026232        var m = mode == LogPersistenceMode.Inherit ? defaultFactory() : mode;
 11026233        return m == LogPersistenceMode.Inherit ? LogPersistenceMode.Include : m;
 234    }
 235
 236    private LogPersistenceConfiguration? ConvertToConfig(object? value)
 237    {
 18305238        if (value == null) return null;
 3759239        if (value is LogPersistenceConfiguration config) return config;
 3759240        var json = JsonSerializer.Serialize(value);
 3759241        return JsonSerializer.Deserialize<LogPersistenceConfiguration>(json, _jsonOptions);
 242    }
 243}