< Summary

Information
Class: Elsa.Workflows.Runtime.Stores.MemoryTriggerStore
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Stores/MemoryTriggerStore.cs
Line coverage
98%
Covered lines: 76
Uncovered lines: 1
Coverable lines: 77
Total lines: 190
Line coverage: 98.7%
Branch coverage
100%
Covered branches: 30
Total branches: 30
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SaveAsync(...)100%11100%
SaveManyAsync(...)100%44100%
FindAsync(...)100%11100%
FindManyAsync(...)100%11100%
FindManyAsync(...)100%11100%
FindManyAsync(...)100%11100%
ReplaceAsync(...)87.5%8894.11%
DeleteManyAsync(...)100%11100%
Filter(...)100%11100%
get_CurrentTenantId()100%44100%
EnsureLogicalKeyAvailable(...)100%44100%
GetMissingLogicalTriggers(...)100%11100%
GetExistingLogicalKeys(...)100%11100%
ApplyCurrentTenant(...)100%66100%
DistinctByLogicalKey()100%44100%
GetLogicalKey(...)100%11100%
get_WorkflowDefinitionId()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Stores/MemoryTriggerStore.cs

#LineLine coverage
 1using Elsa.Common.Entities;
 2using Elsa.Common.Models;
 3using Elsa.Common.Multitenancy;
 4using Elsa.Common.Services;
 5using Elsa.Extensions;
 6using Elsa.Workflows.Runtime.Entities;
 7using Elsa.Workflows.Runtime.Filters;
 8using Elsa.Workflows.Runtime.OrderDefinitions;
 9using JetBrains.Annotations;
 10
 11namespace Elsa.Workflows.Runtime.Stores;
 12
 13/// <inheritdoc />
 14[UsedImplicitly]
 15public class MemoryTriggerStore : ITriggerStore
 16{
 17    private readonly MemoryStore<StoredTrigger> _store;
 18    private readonly ITenantAccessor? _tenantAccessor;
 19
 20    /// <summary>
 21    /// Initializes a new instance of the <see cref="MemoryTriggerStore"/> class.
 22    /// </summary>
 36423    public MemoryTriggerStore(MemoryStore<StoredTrigger> store, ITenantAccessor? tenantAccessor = null)
 24    {
 36425        _store = store;
 36426        _tenantAccessor = tenantAccessor;
 36427    }
 28
 29    /// <inheritdoc />
 30    public ValueTask SaveAsync(StoredTrigger record, CancellationToken cancellationToken = default)
 31    {
 4232        lock (_store.Sync)
 33        {
 4234            ApplyCurrentTenant(record);
 4235            EnsureLogicalKeyAvailable(record);
 8036            _store.Save(record, x => x.Id);
 4037        }
 38
 4039        return new();
 40    }
 41
 42    /// <inheritdoc />
 43    public ValueTask SaveManyAsync(IEnumerable<StoredTrigger> records, CancellationToken cancellationToken = default)
 44    {
 245        lock (_store.Sync)
 46        {
 247            var recordList = records.ToList();
 48
 1049            foreach (var record in recordList)
 350                ApplyCurrentTenant(record);
 51
 252            var uniqueRecords = DistinctByLogicalKey(recordList).ToList();
 53
 754            foreach (var record in uniqueRecords)
 255                EnsureLogicalKeyAvailable(record);
 56
 257            _store.SaveMany(uniqueRecords, x => x.Id);
 158        }
 59
 160        return new();
 61    }
 62
 63    /// <inheritdoc />
 64    public ValueTask<StoredTrigger?> FindAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
 65    {
 1866        var entity = _store.Query(query => Filter(query, filter)).FirstOrDefault();
 967        return new(entity);
 68    }
 69
 70    /// <inheritdoc />
 71    public ValueTask<IEnumerable<StoredTrigger>> FindManyAsync(TriggerFilter filter, CancellationToken cancellationToken
 72    {
 68073        var entities = _store.Query(query => Filter(query, filter));
 34074        return new(entities);
 75    }
 76
 77    public ValueTask<Page<StoredTrigger>> FindManyAsync(TriggerFilter filter, PageArgs pageArgs, CancellationToken cance
 78    {
 7979        return FindManyAsync(filter, pageArgs, new StoredTriggerOrder<string>(x => x.Id, OrderDirection.Ascending), canc
 80    }
 81
 82    public ValueTask<Page<StoredTrigger>> FindManyAsync<TOrderBy>(TriggerFilter filter, PageArgs pageArgs, StoredTrigger
 83    {
 15884        var count = _store.Query(query => Filter(query, filter)).LongCount();
 15885        var result = _store.Query(query => Filter(query, filter).OrderBy(order).Paginate(pageArgs)).ToList();
 7986        return ValueTask.FromResult(Page.Of(result, count));
 87    }
 88
 89    /// <inheritdoc />
 90    public ValueTask ReplaceAsync(IEnumerable<StoredTrigger> removed, IEnumerable<StoredTrigger> added, CancellationToke
 91    {
 31092        lock (_store.Sync)
 93        {
 31094            var removedList = removed.ToList();
 31095            var addedList = added.ToList();
 96
 67097            foreach (var trigger in addedList)
 2598                ApplyCurrentTenant(trigger);
 99
 310100            addedList = DistinctByLogicalKey(addedList).ToList();
 101
 310102            if (removedList.Count > 0)
 10103                _store.DeleteMany(removedList, x => x.Id);
 104
 310105            if (addedList.Count == 0)
 289106                return new();
 107
 21108            var newTriggers = GetMissingLogicalTriggers(addedList);
 109
 21110            if (newTriggers.Count == 0)
 2111                return new();
 112
 40113            _store.SaveMany(newTriggers, x => x.Id);
 19114        }
 115
 19116        return new();
 291117    }
 118
 119    /// <inheritdoc />
 120    public ValueTask<long> DeleteManyAsync(TriggerFilter filter, CancellationToken cancellationToken = default)
 121    {
 2122        lock (_store.Sync)
 123        {
 8124            var ids = _store.Query(query => Filter(query, filter)).Select(x => x.Id).ToList();
 2125            return new(_store.DeleteMany(ids));
 126        }
 2127    }
 128
 129    /// <remarks>
 130    /// Ambient tenant is applied here rather than in <see cref="TriggerFilter.Apply"/>.
 131    /// EF owns that via <c>SetTenantIdFilter</c> / <c>IgnoreQueryFilters</c>; Memory must compensate.
 132    /// </remarks>
 133    private IQueryable<StoredTrigger> Filter(IQueryable<StoredTrigger> queryable, TriggerFilter filter) =>
 509134        filter.Apply(queryable.WhereVisibleToTenant(CurrentTenantId, filter.TenantAgnostic));
 135
 509136    private string CurrentTenantId => _tenantAccessor?.TenantId ?? Tenant.DefaultTenantId;
 137
 138    private void EnsureLogicalKeyAvailable(StoredTrigger record)
 139    {
 44140        var logicalKey = GetLogicalKey(record);
 77141        var existing = _store.Find(x => x.Id != record.Id && GetLogicalKey(x) == logicalKey);
 142
 44143        if (existing is not null)
 144        {
 3145            throw new InvalidOperationException(
 3146                $"A stored trigger already exists for workflow '{record.WorkflowDefinitionId}', hash '{record.Hash}', ac
 147        }
 41148    }
 149
 150    private List<StoredTrigger> GetMissingLogicalTriggers(ICollection<StoredTrigger> triggers)
 151    {
 21152        var existingKeys = GetExistingLogicalKeys(triggers);
 21153        return triggers
 23154            .Where(trigger => !existingKeys.Contains(GetLogicalKey(trigger)))
 21155            .ToList();
 156    }
 157
 158    private HashSet<TriggerLogicalKey> GetExistingLogicalKeys(ICollection<StoredTrigger> triggers)
 159    {
 44160        var workflowDefinitionIds = triggers.Select(x => x.WorkflowDefinitionId).Distinct().ToHashSet();
 21161        return _store
 8162            .FindMany(trigger => workflowDefinitionIds.Contains(trigger.WorkflowDefinitionId))
 21163            .Select(GetLogicalKey)
 21164            .ToHashSet();
 165    }
 166
 167    private void ApplyCurrentTenant(StoredTrigger trigger)
 168    {
 70169        if (trigger.TenantId == Tenant.AgnosticTenantId || _tenantAccessor is null)
 8170            return;
 171
 62172        trigger.TenantId ??= _tenantAccessor.TenantId;
 62173    }
 174
 175    private static IEnumerable<StoredTrigger> DistinctByLogicalKey(IEnumerable<StoredTrigger> triggers)
 176    {
 312177        var seen = new HashSet<TriggerLogicalKey>();
 178
 680179        foreach (var trigger in triggers)
 180        {
 28181            if (seen.Add(GetLogicalKey(trigger)))
 25182                yield return trigger;
 183        }
 312184    }
 185
 186    private static TriggerLogicalKey GetLogicalKey(StoredTrigger trigger) =>
 133187        new(trigger.WorkflowDefinitionId, trigger.Hash, trigger.ActivityId, trigger.TenantId);
 188
 0189    private readonly record struct TriggerLogicalKey(string WorkflowDefinitionId, string? Hash, string ActivityId, strin
 190}

Methods/Properties

.ctor(Elsa.Common.Services.MemoryStore`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>,Elsa.Common.Multitenancy.ITenantAccessor)
SaveAsync(Elsa.Workflows.Runtime.Entities.StoredTrigger,System.Threading.CancellationToken)
SaveManyAsync(System.Collections.Generic.IEnumerable`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>,System.Threading.CancellationToken)
FindAsync(Elsa.Workflows.Runtime.Filters.TriggerFilter,System.Threading.CancellationToken)
FindManyAsync(Elsa.Workflows.Runtime.Filters.TriggerFilter,System.Threading.CancellationToken)
FindManyAsync(Elsa.Workflows.Runtime.Filters.TriggerFilter,Elsa.Common.Models.PageArgs,System.Threading.CancellationToken)
FindManyAsync(Elsa.Workflows.Runtime.Filters.TriggerFilter,Elsa.Common.Models.PageArgs,Elsa.Workflows.Runtime.OrderDefinitions.StoredTriggerOrder`1<TOrderBy>,System.Threading.CancellationToken)
ReplaceAsync(System.Collections.Generic.IEnumerable`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>,System.Collections.Generic.IEnumerable`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>,System.Threading.CancellationToken)
DeleteManyAsync(Elsa.Workflows.Runtime.Filters.TriggerFilter,System.Threading.CancellationToken)
Filter(System.Linq.IQueryable`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>,Elsa.Workflows.Runtime.Filters.TriggerFilter)
get_CurrentTenantId()
EnsureLogicalKeyAvailable(Elsa.Workflows.Runtime.Entities.StoredTrigger)
GetMissingLogicalTriggers(System.Collections.Generic.ICollection`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>)
GetExistingLogicalKeys(System.Collections.Generic.ICollection`1<Elsa.Workflows.Runtime.Entities.StoredTrigger>)
ApplyCurrentTenant(Elsa.Workflows.Runtime.Entities.StoredTrigger)
DistinctByLogicalKey()
GetLogicalKey(Elsa.Workflows.Runtime.Entities.StoredTrigger)
get_WorkflowDefinitionId()