< Summary

Information
Class: Elsa.Workflows.Runtime.TriggerIndexer
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Services/TriggerIndexer.cs
Line coverage
93%
Covered lines: 99
Uncovered lines: 7
Coverable lines: 106
Total lines: 210
Line coverage: 93.3%
Branch coverage
83%
Covered branches: 20
Total branches: 24
Branch coverage: 83.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%
DeleteTriggersAsync()87.5%8891.66%
IndexTriggersAsync()100%11100%
IndexTriggersAsync()50%22100%
GetTriggersAsync()100%11100%
DeleteTriggersAsync()100%11100%
GetCurrentTriggersAsync()100%11100%
GetTriggersInternalAsync()100%88100%
CreateWorkflowTriggersAsync()50%4485.71%
TryGetTriggerDataAsync()100%1133.33%

File(s)

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

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using Elsa.Expressions.Contracts;
 3using Elsa.Extensions;
 4using Elsa.Mediator.Contracts;
 5using Elsa.Workflows.Activities;
 6using Elsa.Workflows.Helpers;
 7using Elsa.Workflows.Management;
 8using Elsa.Workflows.Management.Entities;
 9using Elsa.Workflows.Runtime.Comparers;
 10using Elsa.Workflows.Runtime.Entities;
 11using Elsa.Workflows.Runtime.Filters;
 12using Elsa.Workflows.Runtime.Notifications;
 13using Microsoft.Extensions.Logging;
 14using Open.Linq.AsyncExtensions;
 15
 16namespace Elsa.Workflows.Runtime;
 17
 18/// <inheritdoc />
 19public class TriggerIndexer : ITriggerIndexer
 20{
 21    private readonly IActivityVisitor _activityVisitor;
 22    private readonly IWorkflowDefinitionService _workflowDefinitionService;
 23    private readonly IExpressionEvaluator _expressionEvaluator;
 24    private readonly IIdentityGenerator _identityGenerator;
 25    private readonly ITriggerStore _triggerStore;
 26    private readonly IActivityRegistry _activityRegistry;
 27    private readonly INotificationSender _notificationSender;
 28    private readonly IServiceProvider _serviceProvider;
 29    private readonly IStimulusHasher _hasher;
 30    private readonly ILogger _logger;
 31
 32    /// <summary>
 33    /// Constructor.
 34    /// </summary>
 47835    public TriggerIndexer(
 47836        IActivityVisitor activityVisitor,
 47837        IWorkflowDefinitionService workflowDefinitionService,
 47838        IExpressionEvaluator expressionEvaluator,
 47839        IIdentityGenerator identityGenerator,
 47840        ITriggerStore triggerStore,
 47841        IActivityRegistry activityRegistry,
 47842        INotificationSender notificationSender,
 47843        IServiceProvider serviceProvider,
 47844        IStimulusHasher hasher,
 47845        ILogger<TriggerIndexer> logger)
 46    {
 47847        _activityVisitor = activityVisitor;
 47848        _expressionEvaluator = expressionEvaluator;
 47849        _identityGenerator = identityGenerator;
 47850        _triggerStore = triggerStore;
 47851        _activityRegistry = activityRegistry;
 47852        _notificationSender = notificationSender;
 47853        _serviceProvider = serviceProvider;
 47854        _hasher = hasher;
 47855        _logger = logger;
 47856        _workflowDefinitionService = workflowDefinitionService;
 47857    }
 58
 59    /// <inheritdoc />
 60    public async Task DeleteTriggersAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
 61    {
 662        var triggers = (await _triggerStore.FindManyAsync(filter, cancellationToken)).ToList();
 1363        var workflowDefinitionVersionIds = triggers.Select(x => x.WorkflowDefinitionVersionId).Distinct().ToList();
 64
 2665        foreach (var workflowDefinitionVersionId in workflowDefinitionVersionIds)
 66        {
 67            try
 68            {
 769                var workflowGraph = await _workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionVersionId,
 70
 371                if (workflowGraph == null)
 072                    continue;
 73
 374                await DeleteTriggersAsync(workflowGraph.Workflow, cancellationToken);
 375            }
 476            catch (Exception ex)
 77            {
 478                _logger.LogWarning(ex, "Failed to load workflow graph for workflow definition version {WorkflowDefinitio
 479            }
 780        }
 681    }
 82
 83    /// <inheritdoc />
 84    public async Task<IndexedWorkflowTriggers> IndexTriggersAsync(WorkflowDefinition definition, CancellationToken cance
 85    {
 63786        var workflowGraph = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken);
 63787        return await IndexTriggersAsync(workflowGraph.Workflow, cancellationToken);
 63788    }
 89
 90    /// <inheritdoc />
 91    public async Task<IndexedWorkflowTriggers> IndexTriggersAsync(Workflow workflow, CancellationToken cancellationToken
 92    {
 93        // Get current triggers
 63794        var currentTriggers = await GetCurrentTriggersAsync(workflow.Identity.DefinitionId, cancellationToken).ToList();
 95
 96        // Collect new triggers **if the workflow is published**.
 63797        var newTriggers = workflow.Publication.IsPublished
 63798            ? await GetTriggersInternalAsync(workflow, cancellationToken).ToListAsync(cancellationToken)
 63799            : new(0);
 100
 101        // Diff triggers.
 637102        var diff = Diff.For(currentTriggers, newTriggers, new WorkflowTriggerEqualityComparer());
 103
 104        // Replace triggers for the specified workflow.
 637105        await _triggerStore.ReplaceAsync(diff.Removed, diff.Added, cancellationToken);
 106
 637107        var indexedWorkflow = new IndexedWorkflowTriggers(workflow, diff.Added, diff.Removed, diff.Unchanged);
 108
 109        // Publish event.
 637110        await _notificationSender.SendAsync(new WorkflowTriggersIndexed(indexedWorkflow), cancellationToken);
 637111        return indexedWorkflow;
 637112    }
 113
 114    /// <inheritdoc />
 115    public async Task<IEnumerable<StoredTrigger>> GetTriggersAsync(Workflow workflow, CancellationToken cancellationToke
 116    {
 54117        return await GetTriggersInternalAsync(workflow, cancellationToken).ToListAsync(cancellationToken);
 54118    }
 119
 120    private async Task DeleteTriggersAsync(Workflow workflow, CancellationToken cancellationToken = default)
 121    {
 3122        var emptyTriggerList = new List<StoredTrigger>(0);
 3123        var currentTriggers = await GetCurrentTriggersAsync(workflow.Identity.DefinitionId, cancellationToken).ToList();
 3124        var diff = Diff.For(currentTriggers, emptyTriggerList, new WorkflowTriggerEqualityComparer());
 3125        await _triggerStore.ReplaceAsync(diff.Removed, diff.Added, cancellationToken);
 3126        var indexedWorkflow = new IndexedWorkflowTriggers(workflow, emptyTriggerList, currentTriggers, emptyTriggerList)
 3127        await _notificationSender.SendAsync(new WorkflowTriggersIndexed(indexedWorkflow), cancellationToken);
 3128    }
 129
 130    private async Task<IEnumerable<StoredTrigger>> GetCurrentTriggersAsync(string workflowDefinitionId, CancellationToke
 131    {
 640132        var filter = new TriggerFilter
 640133        {
 640134            WorkflowDefinitionId = workflowDefinitionId
 640135        };
 640136        return await _triggerStore.FindManyAsync(filter, cancellationToken);
 640137    }
 138
 139    private async IAsyncEnumerable<StoredTrigger> GetTriggersInternalAsync(Workflow workflow, [EnumeratorCancellation] C
 140    {
 691141        var context = new WorkflowIndexingContext(workflow, cancellationToken);
 691142        var nodes = await _activityVisitor.VisitAsync(workflow.Root, cancellationToken);
 143
 144        // Get a list of trigger activities that are configured as "startable".
 691145        var triggerActivities = nodes
 691146            .Flatten()
 2414147            .Where(x => x.Activity.GetCanStartWorkflow() && x.Activity is ITrigger)
 83148            .Select(x => x.Activity)
 691149            .Cast<ITrigger>()
 691150            .ToList();
 151
 152        // For each trigger activity, create a trigger.
 1548153        foreach (var triggerActivity in triggerActivities)
 154        {
 83155            var triggers = await CreateWorkflowTriggersAsync(context, triggerActivity);
 156
 332157            foreach (var trigger in triggers)
 83158                yield return trigger;
 159        }
 691160    }
 161
 162    private async Task<ICollection<StoredTrigger>> CreateWorkflowTriggersAsync(WorkflowIndexingContext context, ITrigger
 163    {
 83164        var workflow = context.Workflow;
 83165        var cancellationToken = context.CancellationToken;
 83166        var activityTypeName = trigger.Type;
 83167        var triggerDescriptor = _activityRegistry.Find(activityTypeName, trigger.Version);
 168
 83169        if (triggerDescriptor == null)
 170        {
 0171            _logger.LogWarning("Could not find activity descriptor for activity type {ActivityType}", activityTypeName);
 0172            return new List<StoredTrigger>(0);
 173        }
 174
 83175        var expressionExecutionContext = await trigger.CreateExpressionExecutionContextAsync(triggerDescriptor, _service
 83176        var triggerIndexingContext = new TriggerIndexingContext(context, expressionExecutionContext, trigger, cancellati
 83177        var triggerData = await TryGetTriggerDataAsync(trigger, triggerIndexingContext);
 83178        var triggerName = triggerIndexingContext.TriggerName;
 179
 180        // If no trigger payloads were returned, create a null payload.
 83181        if (!triggerData.Any()) triggerData.Add(null!);
 182
 166183        var triggers = triggerData.Select(payload => new StoredTrigger
 166184        {
 166185            Id = _identityGenerator.GenerateId(),
 166186            WorkflowDefinitionId = workflow.Identity.DefinitionId,
 166187            WorkflowDefinitionVersionId = workflow.Identity.Id,
 166188            Name = triggerName,
 166189            ActivityId = trigger.Id,
 166190            Hash = _hasher.Hash(triggerName, payload),
 166191            Payload = payload
 166192        });
 193
 83194        return triggers.ToList();
 83195    }
 196
 197    private async Task<List<object>> TryGetTriggerDataAsync(ITrigger trigger, TriggerIndexingContext context)
 198    {
 199        try
 200        {
 83201            return (await trigger.GetTriggerPayloadsAsync(context)).ToList();
 202        }
 0203        catch (Exception e)
 204        {
 0205            _logger.LogWarning(e, "Failed to get trigger data for activity {ActivityId}", trigger.Id);
 0206        }
 207
 0208        return new(0);
 83209    }
 210}