diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs index d20658e2e..a3810feb1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules; -internal sealed class RuleQueueWriter : IAsyncDisposable +public sealed class RuleQueueWriter : IAsyncDisposable { private readonly List writes = []; private readonly IRuleEventRepository ruleEventRepository; @@ -38,10 +38,14 @@ internal sealed class RuleQueueWriter : IAsyncDisposable { writes.Add(new RuleEventWrite(result.Job, Error: result.EnrichmentError)); } - else + else if (result.SkipReason is SkipReason.None or SkipReason.Disabled) { writes.Add(new RuleEventWrite(result.Job, result.Job.Created)); } + else + { + return false; + } if (result.Rule != null) { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleQueueWriterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleQueueWriterTests.cs new file mode 100644 index 000000000..056fd86a5 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleQueueWriterTests.cs @@ -0,0 +1,173 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Elasticsearch.Net.Specification.CrossClusterReplicationApi; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; +using Squidex.Domain.Apps.Entities.Rules.Repositories; +using Squidex.Domain.Apps.Entities.TestHelpers; + +namespace Squidex.Domain.Apps.Entities.Rules; + +public class RuleQueueWriterTests : GivenContext +{ + private readonly IRuleEventRepository ruleEventRepository = A.Fake(); + private readonly IRuleUsageTracker ruleUsageTracker = A.Fake(); + private readonly RuleQueueWriter sut; + + public RuleQueueWriterTests() + { + sut = new RuleQueueWriter(ruleEventRepository, ruleUsageTracker, null); + } + + [Fact] + public async Task Should_not_enqueue_result_without_job() + { + var result = new JobResult + { + SkipReason = SkipReason.None, + }; + + var writes = await EnqueueAndFlushAsync(result); + + Assert.Empty(writes); + } + + [Theory] + [InlineData(SkipReason.ConditionDoesNotMatch)] + [InlineData(SkipReason.ConditionPrecheckDoesNotMatch)] + [InlineData(SkipReason.FromRule)] + [InlineData(SkipReason.NoAction)] + [InlineData(SkipReason.NoTrigger)] + [InlineData(SkipReason.TooOld)] + [InlineData(SkipReason.WrongEvent)] + [InlineData(SkipReason.WrongEventForTrigger)] + public async Task Should_not_enqueue_skipped_events_without_error(SkipReason reason) + { + var result = new JobResult + { + SkipReason = reason, + EnrichedEvent = new EnrichedManualEvent(), + EnrichmentError = null, + Job = new RuleJob() + }; + + var writes = await EnqueueAndFlushAsync(result); + + Assert.Empty(writes); + } + + [Fact] + public async Task Should_enqueue_with_failed_reason() + { + var result = new JobResult + { + SkipReason = SkipReason.Failed, + EnrichedEvent = new EnrichedManualEvent(), + EnrichmentError = null, + Job = new RuleJob() + }; + + var writes = await EnqueueAndFlushAsync(result); + + Assert.Equal(new[] + { + new RuleEventWrite(result.Job, null, null) + }, writes); + } + + [Fact] + public async Task Should_enqueue_with_error() + { + var result = new JobResult + { + SkipReason = SkipReason.Failed, + EnrichedEvent = new EnrichedManualEvent(), + EnrichmentError = new InvalidOperationException(), + Job = new RuleJob() + }; + + var writes = await EnqueueAndFlushAsync(result); + + Assert.Equal(new[] + { + new RuleEventWrite(result.Job, null, result.EnrichmentError) + }, writes); + } + + [Fact] + public async Task Should_enqueue_success() + { + var result = new JobResult + { + SkipReason = SkipReason.None, + EnrichedEvent = new EnrichedManualEvent(), + EnrichmentError = null, + Job = new RuleJob() + }; + + var writes = await EnqueueAndFlushAsync(result); + + Assert.Equal(new[] + { + new RuleEventWrite(result.Job, result.Job.Created, null) + }, writes); + } + + [Fact] + public async Task Should_enqueue_disabled() + { + var result = new JobResult + { + SkipReason = SkipReason.Disabled, + EnrichedEvent = new EnrichedManualEvent(), + EnrichmentError = null, + Job = new RuleJob() + }; + + var writes = await EnqueueAndFlushAsync(result); + + Assert.Equal(new[] + { + new RuleEventWrite(result.Job, result.Job.Created, null) + }, writes); + } + + [Fact] + public async Task Should_write_batched() + { + var result = new JobResult + { + SkipReason = SkipReason.Disabled, + EnrichedEvent = new EnrichedManualEvent(), + EnrichmentError = null, + Job = new RuleJob() + }; + + for (var i = 0; i < 250; i++) + { + await sut.WriteAsync(result); + } + + A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) + .MustHaveHappenedANumberOfTimesMatching(x => x == 2); + } + + private async Task EnqueueAndFlushAsync(JobResult result) + { + var writes = Array.Empty(); + + A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) + .Invokes(x => { writes = x.GetArgument>(0)!.ToArray(); }); + + await sut.WriteAsync(result); + await sut.FlushAsync(); + + return writes; + } +} diff --git a/frontend/src/app/features/assets/pages/assets-filters-page.component.html b/frontend/src/app/features/assets/pages/assets-filters-page.component.html index 85db6b121..e85abee95 100644 --- a/frontend/src/app/features/assets/pages/assets-filters-page.component.html +++ b/frontend/src/app/features/assets/pages/assets-filters-page.component.html @@ -1,4 +1,4 @@ - +

{{ 'common.tags' | sqxTranslate }}

+ -
- - +
{{ 'rules.runningRule' | sqxTranslate: { name: runningRule.name || 'Unnamed Rule' } }} + + {{ 'common.cancel' | sqxTranslate }}