Browse Source

Started with usage trigger.

pull/342/head
Sebastian Stehle 7 years ago
parent
commit
40f3b3019b
  1. 13
      extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTriggerV2.cs
  4. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerV2.cs
  5. 24
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/UsageTrigger.cs
  6. 6
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEntityEvent.cs
  7. 11
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs
  8. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUsageExceededEvent.cs
  9. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEvent.cs
  10. 5
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs
  11. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  12. 8
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  13. 16
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs
  14. 5
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs
  15. 18
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
  16. 51
      src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs
  17. 5
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs
  18. 25
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/IUsageTrackerGrain.cs
  19. 143
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs
  20. 69
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs
  21. 19
      src/Squidex.Domain.Apps.Events/AppUsageExceeded.cs
  22. 7
      src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  23. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs
  24. 26
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageTriggerDto.cs
  25. 18
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  26. 41
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs
  27. 77
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs

13
extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Infrastructure;
namespace Squidex.Extensions.Actions
@ -21,20 +22,26 @@ namespace Squidex.Extensions.Actions
private static readonly Dictionary<string, RuleElement> ActionTypes = new Dictionary<string, RuleElement>();
private static readonly Dictionary<string, RuleElement> TriggerTypes = new Dictionary<string, RuleElement>
{
["ContentChanged"] = new RuleElement
[ContentChangedTriggerV2.Name] = new RuleElement
{
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'><path d='M21.875 28H6.125A6.087 6.087 0 0 1 0 21.875V6.125A6.087 6.087 0 0 1 6.125 0h15.75A6.087 6.087 0 0 1 28 6.125v15.75A6.088 6.088 0 0 1 21.875 28zM6.125 1.75A4.333 4.333 0 0 0 1.75 6.125v15.75a4.333 4.333 0 0 0 4.375 4.375h15.75a4.333 4.333 0 0 0 4.375-4.375V6.125a4.333 4.333 0 0 0-4.375-4.375H6.125z'/><path d='M13.125 12.25H7.35c-1.575 0-2.888-1.313-2.888-2.888V7.349c0-1.575 1.313-2.888 2.888-2.888h5.775c1.575 0 2.887 1.313 2.887 2.888v2.013c0 1.575-1.312 2.888-2.887 2.888zM7.35 6.212c-.613 0-1.138.525-1.138 1.138v2.012A1.16 1.16 0 0 0 7.35 10.5h5.775a1.16 1.16 0 0 0 1.138-1.138V7.349a1.16 1.16 0 0 0-1.138-1.138H7.35zM22.662 16.713H5.337c-.525 0-.875-.35-.875-.875s.35-.875.875-.875h17.237c.525 0 .875.35.875.875s-.35.875-.787.875zM15.138 21.262h-9.8c-.525 0-.875-.35-.875-.875s.35-.875.875-.875h9.713c.525 0 .875.35.875.875s-.35.875-.787.875z'/></svg>",
IconColor = "#3389ff",
Display = "Content changed",
Description = "For content changes like created, updated, published, unpublished..."
},
["AssetChanged"] = new RuleElement
[AssetChangedTriggerV2.Name] = new RuleElement
{
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'><path d='M21.875 28H6.125A6.087 6.087 0 0 1 0 21.875V6.125A6.087 6.087 0 0 1 6.125 0h15.75A6.087 6.087 0 0 1 28 6.125v15.75A6.088 6.088 0 0 1 21.875 28zM6.125 1.75A4.333 4.333 0 0 0 1.75 6.125v15.75a4.333 4.333 0 0 0 4.375 4.375h15.75a4.333 4.333 0 0 0 4.375-4.375V6.125a4.333 4.333 0 0 0-4.375-4.375H6.125z'/><path d='M21.088 23.537H9.1c-.35 0-.612-.175-.787-.525s-.088-.7.088-.962l8.225-9.713c.175-.175.438-.35.7-.35s.525.175.7.35l5.25 7.525c.088.087.088.175.088.262.438 1.225.087 2.012-.175 2.45-.613.875-1.925.963-2.1.963zm-10.063-1.75h10.15c.175 0 .612-.088.7-.262.088-.088.088-.35 0-.7l-4.55-6.475-6.3 7.438zM9.1 13.737c-2.1 0-3.85-1.75-3.85-3.85S7 6.037 9.1 6.037s3.85 1.75 3.85 3.85-1.663 3.85-3.85 3.85zm0-5.949c-1.138 0-2.1.875-2.1 2.1s.962 2.1 2.1 2.1 2.1-.962 2.1-2.1-.875-2.1-2.1-2.1z'/></svg>",
IconColor = "#3389ff",
Display = "Asset changed",
Description = "For asset changes like uploaded, updated, renamed, deleted..."
},
[UsageTrigger.Name] = new RuleElement
{
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M21.2 11.4c-.2 0-.4-.1-.6-.2-.5-.3-.6-.9-.3-1.4L22 7.2c.3-.5.9-.6 1.4-.3.6.4.7 1.1.4 1.5L22.1 11c-.2.3-.5.4-.9.4zM16 20.9h-.2c-1-.1-2-.6-2.5-1.5l-6-8.7c-.3-.3-.3-.8 0-1.2.3-.3.8-.4 1.2-.2l9.2 5.4c.9.5 1.5 1.4 1.6 2.4.1 1-.2 2-.9 2.8-.6.7-1.5 1-2.4 1zm-4.6-7.5l3.4 5c.2.3.6.6 1 .6s.8-.1 1.1-.4c.3-.3.4-.7.3-1.1-.1-.4-.3-.7-.6-1zM25.9 32H6.1C2.8 32 0 29.2 0 25.9v-10C0 7.1 7.1 0 15.8 0 24.8 0 32 7.2 32 16.2v9.7c0 3.3-2.8 6.1-6.1 6.1zM15.8 2C8.2 2 2 8.2 2 15.8v10C2 28.1 3.9 30 6.1 30h19.7c2.3 0 4.1-1.9 4.1-4.1v-9.7C30 8.4 23.6 2 15.8 2z'/></svg>",
IconColor = "#3389ff",
Display = "Usage limitations",
Description = "When monthly API calls exceeed a specified limit..."
}
};

2
src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs

@ -14,5 +14,7 @@ namespace Squidex.Domain.Apps.Core.Rules
T Visit(AssetChangedTriggerV2 trigger);
T Visit(ContentChangedTriggerV2 trigger);
T Visit(UsageTrigger trigger);
}
}

2
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTriggerV2.cs

@ -12,6 +12,8 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers
[TypeName(nameof(AssetChangedTriggerV2))]
public sealed class AssetChangedTriggerV2 : RuleTrigger
{
public const string Name = "AssetChanged";
public string Condition { get; set; }
public override T Accept<T>(IRuleTriggerVisitor<T> visitor)

2
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerV2.cs

@ -13,6 +13,8 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers
[TypeName(nameof(ContentChangedTriggerV2))]
public sealed class ContentChangedTriggerV2 : RuleTrigger
{
public const string Name = "ContentChanged";
public ReadOnlyCollection<ContentChangedTriggerSchemaV2> Schemas { get; set; }
public bool HandleAll { get; set; }

24
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/UsageTrigger.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Triggers
{
[TypeName(nameof(UsageTrigger))]
public sealed class UsageTrigger : RuleTrigger
{
public const string Name = "Usage";
public int Limit { get; set; }
public override T Accept<T>(IRuleTriggerVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

6
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEntityEvent.cs

@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public abstract class EnrichedEntityEvent : EnrichedEvent
public abstract class EnrichedEntityEvent : EnrichedUserEvent
{
public Guid Id { get; set; }
@ -23,9 +23,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
public RefToken LastModifiedBy { get; set; }
public override void CalculatePartition()
public override long Partition
{
Partition = Id.GetHashCode();
get { return Id.GetHashCode(); }
}
}
}

11
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs

@ -6,10 +6,8 @@
// ==========================================================================
using System;
using System.Runtime.Serialization;
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
@ -17,19 +15,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public NamedId<Guid> AppId { get; set; }
public RefToken Actor { get; set; }
public Instant Timestamp { get; set; }
public string Name { get; set; }
public long Version { get; set; }
public long Partition { get; set; }
[IgnoreDataMember]
public IUser User { get; set; }
public abstract void CalculatePartition();
public abstract long Partition { get; }
}
}

21
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUsageExceededEvent.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public sealed class EnrichedUsageExceededEvent : EnrichedEvent
{
public long Current { get; set; }
public long Limit { get; set; }
public override long Partition
{
get { return AppId.GetHashCode(); }
}
}
}

21
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEvent.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Runtime.Serialization;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public abstract class EnrichedUserEvent : EnrichedEvent
{
public RefToken Actor { get; set; }
[IgnoreDataMember]
public IUser User { get; set; }
}
}

5
src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.EventSourcing;
@ -16,8 +17,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
Type TriggerType { get; }
bool Triggers(EnrichedEvent @event, RuleTrigger trigger);
Task<bool> TriggersAsync(EnrichedEvent @event, RuleTrigger trigger);
bool Triggers(IEvent @event, RuleTrigger trigger);
Task<bool> TriggersAsync(IEvent @event, RuleTrigger trigger);
}
}

21
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -224,17 +224,32 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static string UserName(EnrichedEvent @event)
{
return @event.User?.DisplayName() ?? Fallback;
if (@event is EnrichedUserEvent userEvent)
{
return userEvent.User?.DisplayName() ?? Fallback;
}
return Fallback;
}
private static string UserId(EnrichedEvent @event)
{
return @event.User?.Id ?? Fallback;
if (@event is EnrichedUserEvent userEvent)
{
return userEvent.User?.Id ?? Fallback;
}
return Fallback;
}
private static string UserEmail(EnrichedEvent @event)
{
return @event.User?.Email ?? Fallback;
if (@event is EnrichedUserEvent userEvent)
{
return userEvent.User?.Email ?? Fallback;
}
return Fallback;
}
private static string CalculateData(NamedContentData data, Match match)

8
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return null;
}
if (!triggerHandler.Triggers(@event.Payload, rule.Trigger))
if (!await triggerHandler.TriggersAsync(@event.Payload, rule.Trigger))
{
return null;
}
@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var enrichedEvent = await eventEnricher.EnrichAsync(appEventEnvelope);
if (!triggerHandler.Triggers(enrichedEvent, rule.Trigger))
if (!await triggerHandler.TriggersAsync(enrichedEvent, rule.Trigger))
{
return null;
}
@ -116,14 +116,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var json = jsonSerializer.Serialize(actionData.Data);
enrichedEvent.CalculatePartition();
var job = new RuleJob
{
JobId = Guid.NewGuid(),
ActionName = actionName,
ActionData = json,
AppId = appEvent.AppId.Id,
AppId = enrichedEvent.AppId.Id,
Created = now,
EventName = enrichedEvent.Name,
ExecutionPartition = enrichedEvent.Partition,

16
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs

@ -6,9 +6,11 @@
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.HandleRules
{
@ -22,21 +24,21 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TTrigger); }
}
bool IRuleTriggerHandler.Triggers(EnrichedEvent @event, RuleTrigger trigger)
Task<bool> IRuleTriggerHandler.TriggersAsync(EnrichedEvent @event, RuleTrigger trigger)
{
return @event is TEnrichedEvent e && Triggers(e, (TTrigger)trigger);
return @event is TEnrichedEvent e ? TriggersAsync(e, (TTrigger)trigger) : TaskHelper.False;
}
bool IRuleTriggerHandler.Triggers(IEvent @event, RuleTrigger trigger)
Task<bool> IRuleTriggerHandler.TriggersAsync(IEvent @event, RuleTrigger trigger)
{
return @event is TEvent e && Triggers(e, (TTrigger)trigger);
return @event is TEvent e ? TriggersAsync(e, (TTrigger)trigger) : TaskHelper.False;
}
protected abstract bool Triggers(TEnrichedEvent @event, TTrigger trigger);
protected abstract Task<bool> TriggersAsync(TEnrichedEvent @event, TTrigger trigger);
protected virtual bool Triggers(TEvent @event, TTrigger trigger)
protected virtual Task<bool> TriggersAsync(TEvent @event, TTrigger trigger)
{
return true;
return TaskHelper.True;
}
}
}

5
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Core.Scripting;
@ -24,9 +25,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
this.scriptEngine = scriptEngine;
}
protected override bool Triggers(EnrichedAssetEvent @event, AssetChangedTriggerV2 trigger)
protected override Task<bool> TriggersAsync(EnrichedAssetEvent @event, AssetChangedTriggerV2 trigger)
{
return string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", @event, trigger.Condition);
return Task.FromResult(string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", @event, trigger.Condition));
}
}
}

18
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs

@ -6,11 +6,13 @@
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
@ -25,11 +27,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
this.scriptEngine = scriptEngine;
}
protected override bool Triggers(ContentEvent @event, ContentChangedTriggerV2 trigger)
protected override Task<bool> TriggersAsync(ContentEvent @event, ContentChangedTriggerV2 trigger)
{
if (trigger.HandleAll)
{
return true;
return TaskHelper.True;
}
if (trigger.Schemas != null)
@ -38,19 +40,19 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
if (MatchsSchema(schema, @event.SchemaId))
{
return true;
return TaskHelper.True;
}
}
}
return false;
return TaskHelper.False;
}
protected override bool Triggers(EnrichedContentEvent @event, ContentChangedTriggerV2 trigger)
protected override Task<bool> TriggersAsync(EnrichedContentEvent @event, ContentChangedTriggerV2 trigger)
{
if (trigger.HandleAll)
{
return true;
return TaskHelper.True;
}
if (trigger.Schemas != null)
@ -59,12 +61,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
if (MatchsSchema(schema, @event.SchemaId) && MatchsCondition(schema, @event))
{
return true;
return TaskHelper.True;
}
}
}
return false;
return TaskHelper.False;
}
private static bool MatchsSchema(ContentChangedTriggerSchemaV2 schema, NamedId<Guid> eventId)

51
src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs

@ -46,26 +46,38 @@ namespace Squidex.Domain.Apps.Entities.Rules
{
Guard.NotNull(@event, nameof(@event));
if (@event.Payload is ContentEvent contentEvent)
switch (@event.Payload)
{
var result = new EnrichedContentEvent();
case ContentEvent contentEvent:
{
var result = new EnrichedContentEvent();
await Task.WhenAll(
EnrichContentAsync(result, contentEvent, @event),
EnrichDefaultAsync(result, @event));
await Task.WhenAll(
EnrichContentAsync(result, contentEvent, @event),
EnrichDefaultAsync(result, @event));
return result;
}
return result;
}
if (@event.Payload is AssetEvent assetEvent)
{
var result = new EnrichedAssetEvent();
case AssetEvent assetEvent:
{
var result = new EnrichedAssetEvent();
await Task.WhenAll(
EnrichAssetAsync(result, assetEvent, @event),
EnrichDefaultAsync(result, @event));
await Task.WhenAll(
EnrichAssetAsync(result, assetEvent, @event),
EnrichDefaultAsync(result, @event));
return result;
}
return result;
case AppUsageExceeded usageExceeded:
{
var result = new EnrichedUsageExceededEvent { Current = usageExceeded.Current, Limit = usageExceeded.Limit };
await EnrichDefaultAsync(result, @event);
return result;
}
}
return null;
@ -149,17 +161,20 @@ namespace Squidex.Domain.Apps.Entities.Rules
{
result.Timestamp = @event.Headers.Timestamp();
if (@event.Payload is SquidexEvent squidexEvent)
if (result is EnrichedUserEvent userEvent)
{
result.Actor = squidexEvent.Actor;
if (@event.Payload is SquidexEvent squidexEvent)
{
userEvent.Actor = squidexEvent.Actor;
}
userEvent.User = await FindUserAsync(userEvent.Actor);
}
if (@event.Payload is AppEvent appEvent)
{
result.AppId = appEvent.AppId;
}
result.User = await FindUserAsync(result.Actor);
}
private Task<IUser> FindUserAsync(RefToken actor)

5
src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs

@ -40,6 +40,11 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
return Task.FromResult(Enumerable.Empty<ValidationError>());
}
public Task<IEnumerable<ValidationError>> Visit(UsageTrigger trigger)
{
return Task.FromResult(Enumerable.Empty<ValidationError>());
}
public async Task<IEnumerable<ValidationError>> Visit(ContentChangedTriggerV2 trigger)
{
if (trigger.Schemas != null)

25
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/IUsageTrackerGrain.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
public interface IUsageTrackerGrain : IGrainWithStringKey
{
Task AddTargetAsync(NamedId<Guid> appId, int limits);
Task ActivateTargetAsync(NamedId<Guid> appId);
Task DeactivateTargetAsync(NamedId<Guid> appId);
Task RemoveTargetAsync(NamedId<Guid> appId);
}
}

143
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs

@ -0,0 +1,143 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
public sealed class UsageTrackerGrain : GrainOfString, IRemindable, IUsageTrackerGrain
{
private readonly IStore<string> store;
private readonly IUsageTracker usageTracker;
private IPersistence<State> persistence;
private State state;
public sealed class Target
{
public int Limit { get; set; }
public bool Enabled { get; set; }
public DateTime Triggered { get; set; }
}
[CollectionName("UsageTracker")]
public sealed class State
{
public Dictionary<NamedId<Guid>, Target> Targets { get; set; } = new Dictionary<NamedId<Guid>, Target>();
}
public UsageTrackerGrain(IStore<string> store, IUsageTracker usageTracker)
{
Guard.NotNull(store, nameof(store));
Guard.NotNull(usageTracker, nameof(usageTracker));
this.store = store;
this.usageTracker = usageTracker;
}
public override Task OnActivateAsync(string key)
{
DelayDeactivation(TimeSpan.FromDays(1));
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
persistence = store.WithSnapshotsAndEventSourcing<State, string>(GetType(), key, ApplySnapshot, ApplyEvent);
return persistence.ReadAsync();
}
private void ApplySnapshot(State s)
{
state = s;
}
private void ApplyEvent(Envelope<IEvent> @event)
{
}
public async Task ReceiveReminder(string reminderName, TickStatus status)
{
var today = DateTime.Today;
foreach (var kvp in state.Targets)
{
var appId = kvp.Key;
if (!IsSameMonth(today, kvp.Value.Triggered))
{
var usage = await usageTracker.GetMonthlyCallsAsync(appId.Id.ToString(), today);
var limit = kvp.Value.Limit;
if (usage > limit)
{
kvp.Value.Triggered = today;
var @event = new AppUsageExceeded { AppId = appId, Current = usage, Limit = limit };
await persistence.WriteEventsAsync(new[]
{
Envelope.Create(@event)
});
}
}
}
await persistence.WriteSnapshotAsync(state);
}
private static bool IsSameMonth(DateTime lhs, DateTime rhs)
{
return lhs.Year == rhs.Year && lhs.Month == rhs.Month;
}
public Task ActivateTargetAsync(NamedId<Guid> appId)
{
UpdateTarget(appId, t => t.Enabled = true);
return persistence.WriteSnapshotAsync(state);
}
public Task DeactivateTargetAsync(NamedId<Guid> appId)
{
UpdateTarget(appId, t => t.Enabled = false);
return persistence.WriteSnapshotAsync(state);
}
public Task AddTargetAsync(NamedId<Guid> appId, int limits)
{
UpdateTarget(appId, t => t.Limit = limits);
return persistence.WriteSnapshotAsync(state);
}
public Task RemoveTargetAsync(NamedId<Guid> appId)
{
state.Targets.Remove(appId);
return persistence.WriteSnapshotAsync(state);
}
private void UpdateTarget(NamedId<Guid> appId, Action<Target> updater)
{
updater(state.Targets.GetOrAddNew(appId));;
}
}
}

69
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs

@ -0,0 +1,69 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{
public sealed class UsageTriggerHandler : RuleTriggerHandler<UsageTrigger, AppEvent, EnrichedUsageExceededEvent>
{
private readonly IUsageTrackerGrain usageTrackerGrain;
public UsageTriggerHandler(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
usageTrackerGrain = grainFactory.GetGrain<IUsageTrackerGrain>(SingleGrain.Id);
}
protected override async Task<bool> TriggersAsync(AppEvent @event, UsageTrigger trigger)
{
switch (@event)
{
case RuleDeleted _:
await usageTrackerGrain.RemoveTargetAsync(@event.AppId);
break;
case RuleEnabled _:
await usageTrackerGrain.ActivateTargetAsync(@event.AppId);
break;
case RuleDisabled _:
await usageTrackerGrain.DeactivateTargetAsync(@event.AppId);
break;
case RuleCreated ruleCreated:
if (ruleCreated.Trigger is UsageTrigger createdTrigger)
{
await usageTrackerGrain.AddTargetAsync(ruleCreated.AppId, createdTrigger.Limit);
}
break;
case RuleUpdated ruleUpdated:
if (ruleUpdated.Trigger is UsageTrigger updatedTrigger)
{
await usageTrackerGrain.AddTargetAsync(ruleUpdated.AppId, updatedTrigger.Limit);
}
break;
}
return @event is AppUsageExceeded;
}
protected override Task<bool> TriggersAsync(EnrichedUsageExceededEvent @event, UsageTrigger trigger)
{
return TaskHelper.True;
}
}
}

19
src/Squidex.Domain.Apps.Events/AppUsageExceeded.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events
{
[EventType(nameof(AppUsageExceeded))]
public sealed class AppUsageExceeded : AppEvent
{
public long Current { get; set; }
public long Limit { get; set; }
}
}

7
src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -47,11 +47,16 @@ namespace Squidex.Infrastructure.Commands
protected sealed override Task ReadAsync(Type type, Guid id)
{
persistence = store.WithSnapshotsAndEventSourcing<T, Guid>(GetType(), id, x => snapshot = x, ApplyEvent);
persistence = store.WithSnapshotsAndEventSourcing<T, Guid>(GetType(), id, ApplySnapshot, ApplyEvent);
return persistence.ReadAsync();
}
private void ApplySnapshot(T state)
{
snapshot = state;
}
protected sealed override async Task WriteAsync(Envelope<IEvent>[] events, long previousVersion)
{
if (events.Length > 0)

5
src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs

@ -31,6 +31,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters
return SimpleMapper.Map(trigger, new AssetChangedRuleTriggerDto());
}
public RuleTriggerDto Visit(UsageTrigger trigger)
{
return SimpleMapper.Map(trigger, new UsageTriggerDto());
}
public RuleTriggerDto Visit(ContentChangedTriggerV2 trigger)
{
var schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedRuleTriggerSchemaDto())).ToArray();

26
src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageTriggerDto.cs

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers
{
public sealed class UsageTriggerDto : RuleTriggerDto
{
/// <summary>
/// The number of monthly api calls.
/// </summary>
public int Limit { get; set; }
public override RuleTrigger ToTrigger()
{
return SimpleMapper.Map(this, new UsageTrigger());
}
}
}

18
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs

@ -33,6 +33,8 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
private readonly string actionDump = "MyDump";
private readonly string actionName = "ValidAction";
private readonly string actionDescription = "MyDescription";
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry();
private readonly RuleService sut;
@ -68,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
typeNameRegistry.Map(typeof(ValidAction), actionName);
A.CallTo(() => eventEnricher.EnrichAsync(A<Envelope<AppEvent>>.Ignored))
.Returns(new EnrichedContentEvent());
.Returns(new EnrichedContentEvent { AppId = appId });
A.CallTo(() => ruleActionHandler.ActionType)
.Returns(typeof(ValidAction));
@ -144,7 +146,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var ruleConfig = ValidRule();
var ruleEnvelope = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.Triggers(A<IEvent>.Ignored, ruleConfig.Trigger))
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<IEvent>.Ignored, ruleConfig.Trigger))
.Returns(false);
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -161,10 +163,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var ruleConfig = ValidRule();
var ruleEnvelope = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.Triggers(A<IEvent>.Ignored, ruleConfig.Trigger))
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<IEvent>.Ignored, ruleConfig.Trigger))
.Returns(true);
A.CallTo(() => ruleTriggerHandler.Triggers(A<EnrichedEvent>.Ignored, ruleConfig.Trigger))
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<EnrichedEvent>.Ignored, ruleConfig.Trigger))
.Returns(false);
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -175,7 +177,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public async Task Should_not_create_job_if_too_old()
{
var @event = new ContentCreated { SchemaId = NamedId.Of(Guid.NewGuid(), "my-schema"), AppId = NamedId.Of(Guid.NewGuid(), "my-event") };
var @event = new ContentCreated { SchemaId = schemaId, AppId = appId };
var now = SystemClock.Instance.GetCurrentInstant();
@ -201,7 +203,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public async Task Should_create_job_if_triggered()
{
var @event = new ContentCreated { SchemaId = NamedId.Of(Guid.NewGuid(), "my-schema"), AppId = NamedId.Of(Guid.NewGuid(), "my-event") };
var @event = new ContentCreated { SchemaId = schemaId, AppId = appId };
var now = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds());
@ -213,10 +215,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => clock.GetCurrentInstant())
.Returns(now);
A.CallTo(() => ruleTriggerHandler.Triggers(A<EnrichedEvent>.Ignored, ruleConfig.Trigger))
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<EnrichedEvent>.Ignored, ruleConfig.Trigger))
.Returns(true);
A.CallTo(() => ruleTriggerHandler.Triggers(A<IEvent>.Ignored, ruleConfig.Trigger))
A.CallTo(() => ruleTriggerHandler.TriggersAsync(A<IEvent>.Ignored, ruleConfig.Trigger))
.Returns(true);
A.CallTo(() => ruleActionHandler.CreateJobAsync(A<EnrichedEvent>.Ignored, ruleConfig.Action))

41
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -35,76 +36,76 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
}
[Fact]
public void Should_not_trigger_precheck_when_event_type_not_correct()
public Task Should_not_trigger_precheck_when_event_type_not_correct()
{
TestForCondition(string.Empty, trigger =>
return TestForConditionAsync(string.Empty, async trigger =>
{
var result = sut.Triggers(new ContentCreated(), trigger);
var result = await sut.TriggersAsync(new ContentCreated(), trigger);
Assert.False(result);
});
}
[Fact]
public void Should_trigger_precheck_when_event_type_correct()
public Task Should_trigger_precheck_when_event_type_correct()
{
TestForCondition(string.Empty, trigger =>
return TestForConditionAsync(string.Empty, async trigger =>
{
var result = sut.Triggers(new AssetCreated(), trigger);
var result = await sut.TriggersAsync(new AssetCreated(), trigger);
Assert.True(result);
});
}
[Fact]
public void Should_not_trigger_check_when_event_type_not_correct()
public Task Should_not_trigger_check_when_event_type_not_correct()
{
TestForCondition(string.Empty, trigger =>
return TestForConditionAsync(string.Empty, async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent(), trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent(), trigger);
Assert.False(result);
});
}
[Fact]
public void Should_trigger_check_when_condition_is_empty()
public Task Should_trigger_check_when_condition_is_empty()
{
TestForCondition(string.Empty, trigger =>
return TestForConditionAsync(string.Empty, async trigger =>
{
var result = sut.Triggers(new EnrichedAssetEvent(), trigger);
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger);
Assert.True(result);
});
}
[Fact]
public void Should_trigger_check_when_condition_matchs()
public Task Should_trigger_check_when_condition_matchs()
{
TestForCondition("true", trigger =>
return TestForConditionAsync("true", async trigger =>
{
var result = sut.Triggers(new EnrichedAssetEvent(), trigger);
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger);
Assert.True(result);
});
}
[Fact]
public void Should_not_trigger_check_when_condition_does_not_matchs()
public Task Should_not_trigger_check_when_condition_does_not_matchs()
{
TestForCondition("false", trigger =>
return TestForConditionAsync("false", async trigger =>
{
var result = sut.Triggers(new EnrichedAssetEvent(), trigger);
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger);
Assert.False(result);
});
}
private void TestForCondition(string condition, Action<AssetChangedTriggerV2> action)
private async Task TestForConditionAsync(string condition, Func<AssetChangedTriggerV2, Task> action)
{
var trigger = new AssetChangedTriggerV2 { Condition = condition };
action(trigger);
await action(trigger);
if (string.IsNullOrWhiteSpace(condition))
{

77
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -43,138 +44,138 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
}
[Fact]
public void Should_not_trigger_precheck_when_event_type_not_correct()
public Task Should_not_trigger_precheck_when_event_type_not_correct()
{
TestForTrigger(handleAll: true, schemaId: null, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: true, schemaId: null, condition: null, action: async trigger =>
{
var result = sut.Triggers(new AssetCreated(), trigger);
var result = await sut.TriggersAsync(new AssetCreated(), trigger);
Assert.False(result);
});
}
[Fact]
public void Should_not_trigger_precheck_when_trigger_contains_no_schemas()
public Task Should_not_trigger_precheck_when_trigger_contains_no_schemas()
{
TestForTrigger(handleAll: false, schemaId: null, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: null, condition: null, action: async trigger =>
{
var result = sut.Triggers(new ContentCreated { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.False(result);
});
}
[Fact]
public void Should_trigger_precheck_when_handling_all_events()
public Task Should_trigger_precheck_when_handling_all_events()
{
TestForTrigger(handleAll: true, schemaId: SchemaMatch, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: true, schemaId: SchemaMatch, condition: null, action: async trigger =>
{
var result = sut.Triggers(new ContentCreated { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.True(result);
});
}
[Fact]
public void Should_trigger_precheck_when_condition_is_empty()
public Task Should_trigger_precheck_when_condition_is_empty()
{
TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: async trigger =>
{
var result = sut.Triggers(new ContentCreated { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.True(result);
});
}
[Fact]
public void Should_not_trigger_precheck_when_schema_id_does_not_match()
public Task Should_not_trigger_precheck_when_schema_id_does_not_match()
{
TestForTrigger(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: async trigger =>
{
var result = sut.Triggers(new ContentCreated { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new ContentCreated { SchemaId = SchemaMatch }, trigger);
Assert.False(result);
});
}
[Fact]
public void Should_not_trigger_check_when_event_type_not_correct()
public Task Should_not_trigger_check_when_event_type_not_correct()
{
TestForTrigger(handleAll: true, schemaId: null, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: true, schemaId: null, condition: null, action: async trigger =>
{
var result = sut.Triggers(new EnrichedAssetEvent(), trigger);
var result = await sut.TriggersAsync(new EnrichedAssetEvent(), trigger);
Assert.False(result);
});
}
[Fact]
public void Should_not_trigger_check_when_trigger_contains_no_schemas()
public Task Should_not_trigger_check_when_trigger_contains_no_schemas()
{
TestForTrigger(handleAll: false, schemaId: null, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: null, condition: null, action: async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.False(result);
});
}
[Fact]
public void Should_trigger_check_when_handling_all_events()
public Task Should_trigger_check_when_handling_all_events()
{
TestForTrigger(handleAll: true, schemaId: SchemaMatch, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: true, schemaId: SchemaMatch, condition: null, action: async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.True(result);
});
}
[Fact]
public void Should_trigger_check_when_condition_is_empty()
public Task Should_trigger_check_when_condition_is_empty()
{
TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: string.Empty, action: async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.True(result);
});
}
[Fact]
public void Should_trigger_check_when_condition_matchs()
public Task Should_trigger_check_when_condition_matchs()
{
TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: "true", action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: "true", action: async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.True(result);
});
}
[Fact]
public void Should_not_trigger_check_when_schema_id_does_not_match()
public Task Should_not_trigger_check_when_schema_id_does_not_match()
{
TestForTrigger(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: SchemaNonMatch, condition: null, action: async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.False(result);
});
}
[Fact]
public void Should_not_trigger_check_when_condition_does_not_matchs()
public Task Should_not_trigger_check_when_condition_does_not_matchs()
{
TestForTrigger(handleAll: false, schemaId: SchemaMatch, condition: "false", action: trigger =>
return TestForTriggerAsync(handleAll: false, schemaId: SchemaMatch, condition: "false", action: async trigger =>
{
var result = sut.Triggers(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
var result = await sut.TriggersAsync(new EnrichedContentEvent { SchemaId = SchemaMatch }, trigger);
Assert.False(result);
});
}
private void TestForTrigger(bool handleAll, NamedId<Guid> schemaId, string condition, Action<ContentChangedTriggerV2> action)
private async Task TestForTriggerAsync(bool handleAll, NamedId<Guid> schemaId, string condition, Func<ContentChangedTriggerV2, Task> action)
{
var trigger = new ContentChangedTriggerV2 { HandleAll = handleAll };
@ -189,7 +190,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
});
}
action(trigger);
await action(trigger);
if (string.IsNullOrWhiteSpace(condition))
{

Loading…
Cancel
Save