Browse Source

Schema changed trigger.

pull/346/head
Sebastian Stehle 7 years ago
parent
commit
92fab4fb7c
  1. 2
      extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs
  2. 36
      extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs
  3. 57
      extensions/Squidex.Extensions/Actions/TriggerTypes.cs
  4. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs
  5. 22
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/SchemaChangedTrigger.cs
  6. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs
  7. 20
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs
  8. 15
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs
  9. 18
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventBase.cs
  10. 18
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventType.cs
  11. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEventBase.cs
  12. 16
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/IEnrichedEntityEvent.cs
  13. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs
  14. 10
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  15. 132
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  16. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs
  17. 5
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs
  18. 74
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs
  19. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs
  20. 26
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/SchemaChangedRuleTriggerDto.cs
  21. 4
      src/Squidex/Config/Domain/RuleServices.cs
  22. 1
      src/Squidex/app/features/rules/declarations.ts
  23. 2
      src/Squidex/app/features/rules/module.ts
  24. 7
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  25. 28
      src/Squidex/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html
  26. 6
      src/Squidex/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.scss
  27. 30
      src/Squidex/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts
  28. 4
      src/Squidex/app/framework/angular/modals/modal-target.directive.ts
  29. 11
      src/Squidex/app/framework/angular/modals/tooltip.component.ts
  30. 5
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  31. 148
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs

2
extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs

@ -30,7 +30,7 @@ namespace Squidex.Extensions.Actions.Fastly
protected override (string Description, FastlyJob Data) CreateJob(EnrichedEvent @event, FastlyAction action)
{
var id = @event is EnrichedEntityEvent entityEvent ? entityEvent.Id.ToString() : string.Empty;
var id = @event is IEnrichedEntityEvent entityEvent ? entityEvent.Id.ToString() : string.Empty;
var ruleJob = new FastlyJob
{

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

@ -10,7 +10,6 @@ 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
@ -19,40 +18,12 @@ namespace Squidex.Extensions.Actions
{
private const string ActionSuffix = "Action";
private const string ActionSuffixV2 = "Action";
private const string TriggerSuffix = "Trigger";
private const string TriggerSuffixV2 = "TriggerV2";
private static readonly HashSet<Type> ActionHandlerTypes = new HashSet<Type>();
private static readonly Dictionary<string, RuleElement> ActionTypes = new Dictionary<string, RuleElement>();
private static readonly Dictionary<string, RuleElement> TriggerTypes = new Dictionary<string, RuleElement>
{
[GetTriggerName(typeof(ContentChangedTriggerV2))] = 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..."
},
[GetTriggerName(typeof(AssetChangedTriggerV2))] = 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 (reuploaded), renamed, deleted..."
},
[GetTriggerName(typeof(UsageTrigger))] = 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 exceeded",
Description = "When monthly API calls exceed a specified limit for one time a month..."
}
};
public static IReadOnlyDictionary<string, RuleElement> Triggers
{
get { return TriggerTypes; }
get { return TriggerTypes.All; }
}
public static IReadOnlyDictionary<string, RuleElement> Actions
@ -110,10 +81,5 @@ namespace Squidex.Extensions.Actions
{
return type.TypeName(false, ActionSuffix, ActionSuffixV2);
}
private static string GetTriggerName(Type type)
{
return type.TypeName(false, TriggerSuffix, TriggerSuffixV2);
}
}
}

57
extensions/Squidex.Extensions/Actions/TriggerTypes.cs

@ -0,0 +1,57 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Infrastructure;
namespace Squidex.Extensions.Actions
{
public static class TriggerTypes
{
private const string TriggerSuffix = "Trigger";
private const string TriggerSuffixV2 = "TriggerV2";
public static readonly IReadOnlyDictionary<string, RuleElement> All = new Dictionary<string, RuleElement>
{
[GetTriggerName(typeof(ContentChangedTriggerV2))] = 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..."
},
[GetTriggerName(typeof(AssetChangedTriggerV2))] = 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 (reuploaded), renamed, deleted..."
},
[GetTriggerName(typeof(SchemaChangedTrigger))] = new RuleElement
{
IconImage = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path d='M9.6 0c-.6 0-1 .4-1 1s.4 1 1 1h12.8c.6 0 1.1-.4 1.1-1s-.4-1-1-1H9.6zM6.1 4.3c-.6 0-1 .4-1 1s.4 1 1 1h19.8c.5 0 .9-.4.9-1s-.4-1-1-1H6.1zM7 8.6c-3.9 0-7 3.1-7 7V25c0 3.9 3.1 7 7 7h18c3.9 0 7-3.1 7-7v-9.4c0-3.9-3.1-7-7-7H7zm0 2h18c2.8 0 5 2.2 5 5V25c0 2.8-2.2 5-5 5H7c-2.8 0-5-2.2-5-5v-9.4c0-2.8 2.2-5 5-5zM5.3 13v2c0 2.4 2 4.4 4.4 4.4h12.7c2.4 0 4.4-2 4.4-4.4v-2H25v2c0 1.5-1.2 2.6-2.6 2.6H9.6C8.2 17.7 7 16.5 7 15v-2H5.3z' id='path5869'/></svg>",
IconColor = "#3389ff",
Display = "Schema changed",
Description = "When a schema definition has been created, updated, published or deleted..."
},
[GetTriggerName(typeof(UsageTrigger))] = 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 exceeded",
Description = "When monthly API calls exceed a specified limit for one time a month..."
}
};
private static string GetTriggerName(Type type)
{
return type.TypeName(false, TriggerSuffix, TriggerSuffixV2);
}
}
}

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

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Rules
T Visit(ContentChangedTriggerV2 trigger);
T Visit(SchemaChangedTrigger trigger);
T Visit(UsageTrigger trigger);
}
}

22
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/SchemaChangedTrigger.cs

@ -0,0 +1,22 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Triggers
{
[TypeName(nameof(SchemaChangedTrigger))]
public sealed class SchemaChangedTrigger : RuleTrigger
{
public string Condition { get; set; }
public override T Accept<T>(IRuleTriggerVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

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

@ -5,12 +5,26 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public sealed class EnrichedAssetEvent : EnrichedEntityEvent
public sealed class EnrichedAssetEvent : EnrichedUserEventBase, IEnrichedEntityEvent
{
public EnrichedAssetEventType Type { get; set; }
public Guid Id { get; set; }
public Instant Created { get; set; }
public Instant LastModified { get; set; }
public RefToken CreatedBy { get; set; }
public RefToken LastModifiedBy { get; set; }
public string MimeType { get; set; }
public string FileName { get; set; }
@ -24,5 +38,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
public int? PixelWidth { get; set; }
public int? PixelHeight { get; set; }
public override long Partition
{
get { return Id.GetHashCode(); }
}
}
}

20
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs

@ -5,16 +5,34 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public sealed class EnrichedContentEvent : EnrichedSchemaEvent
public sealed class EnrichedContentEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent
{
public EnrichedContentEventType Type { get; set; }
public Guid Id { get; set; }
public Instant Created { get; set; }
public Instant LastModified { get; set; }
public RefToken CreatedBy { get; set; }
public RefToken LastModifiedBy { get; set; }
public NamedContentData Data { get; set; }
public Status Status { get; set; }
public override long Partition
{
get { return SchemaId.GetHashCode(); }
}
}
}

15
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs

@ -6,12 +6,21 @@
// ==========================================================================
using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public abstract class EnrichedSchemaEvent : EnrichedEntityEvent
public sealed class EnrichedSchemaEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent
{
public NamedId<Guid> SchemaId { get; set; }
public EnrichedSchemaEventType Type { get; set; }
public Guid Id
{
get { return SchemaId.Id; }
}
public override long Partition
{
get { return SchemaId.GetHashCode(); }
}
}
}

18
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEntityEvent.cs → src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventBase.cs

@ -6,26 +6,12 @@
// ==========================================================================
using System;
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public abstract class EnrichedEntityEvent : EnrichedUserEvent
public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase
{
public Guid Id { get; set; }
public Instant Created { get; set; }
public Instant LastModified { get; set; }
public RefToken CreatedBy { get; set; }
public RefToken LastModifiedBy { get; set; }
public override long Partition
{
get { return Id.GetHashCode(); }
}
public NamedId<Guid> SchemaId { get; set; }
}
}

18
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventType.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public enum EnrichedSchemaEventType
{
Created,
Deleted,
Published,
Unpublished,
Updated
}
}

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEvent.cs → src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEventBase.cs

@ -11,7 +11,7 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public abstract class EnrichedUserEvent : EnrichedEvent
public abstract class EnrichedUserEventBase : EnrichedEvent
{
public RefToken Actor { get; set; }

16
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/IEnrichedEntityEvent.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public interface IEnrichedEntityEvent
{
Guid Id { get; }
}
}

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
enrichedEvent.Timestamp = @event.Headers.Timestamp();
if (enrichedEvent is EnrichedUserEvent userEvent)
if (enrichedEvent is EnrichedUserEventBase userEvent)
{
if (@event.Payload is SquidexEvent squidexEvent)
{

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

@ -184,7 +184,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static string SchemaId(EnrichedEvent @event)
{
if (@event is EnrichedSchemaEvent schemaEvent)
if (@event is EnrichedSchemaEventBase schemaEvent)
{
return schemaEvent.SchemaId.Id.ToString();
}
@ -194,7 +194,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static string SchemaName(EnrichedEvent @event)
{
if (@event is EnrichedSchemaEvent schemaEvent)
if (@event is EnrichedSchemaEventBase schemaEvent)
{
return schemaEvent.SchemaId.Name;
}
@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static string UserName(EnrichedEvent @event)
{
if (@event is EnrichedUserEvent userEvent)
if (@event is EnrichedUserEventBase userEvent)
{
return userEvent.User?.DisplayName() ?? Fallback;
}
@ -234,7 +234,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static string UserId(EnrichedEvent @event)
{
if (@event is EnrichedUserEvent userEvent)
if (@event is EnrichedUserEventBase userEvent)
{
return userEvent.User?.Id ?? Fallback;
}
@ -244,7 +244,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static string UserEmail(EnrichedEvent @event)
{
if (@event is EnrichedUserEvent userEvent)
if (@event is EnrichedUserEventBase userEvent)
{
return userEvent.User?.Email ?? Fallback;
}

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

@ -16,6 +16,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
namespace Squidex.Domain.Apps.Core.HandleRules
{
@ -27,6 +28,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private readonly IEventEnricher eventEnricher;
private readonly IJsonSerializer jsonSerializer;
private readonly IClock clock;
private readonly ISemanticLog log;
public RuleService(
IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers,
@ -34,6 +36,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
IEventEnricher eventEnricher,
IJsonSerializer jsonSerializer,
IClock clock,
ISemanticLog log,
TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
@ -42,6 +45,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
Guard.NotNull(eventEnricher, nameof(eventEnricher));
Guard.NotNull(clock, nameof(clock));
Guard.NotNull(log, nameof(log));
this.typeNameRegistry = typeNameRegistry;
@ -53,6 +57,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.jsonSerializer = jsonSerializer;
this.clock = clock;
this.log = log;
}
public virtual async Task<RuleJob> CreateJobAsync(Rule rule, Guid ruleId, Envelope<IEvent> @event)
@ -60,84 +65,95 @@ namespace Squidex.Domain.Apps.Core.HandleRules
Guard.NotNull(rule, nameof(rule));
Guard.NotNull(@event, nameof(@event));
if (!rule.IsEnabled)
try
{
return null;
}
if (!rule.IsEnabled)
{
return null;
}
if (!(@event.Payload is AppEvent))
{
return null;
}
if (!(@event.Payload is AppEvent))
{
return null;
}
var typed = @event.To<AppEvent>();
var typed = @event.To<AppEvent>();
var actionType = rule.Action.GetType();
var actionType = rule.Action.GetType();
if (!ruleTriggerHandlers.TryGetValue(rule.Trigger.GetType(), out var triggerHandler))
{
return null;
}
if (!ruleTriggerHandlers.TryGetValue(rule.Trigger.GetType(), out var triggerHandler))
{
return null;
}
if (!ruleActionHandlers.TryGetValue(actionType, out var actionHandler))
{
return null;
}
if (!ruleActionHandlers.TryGetValue(actionType, out var actionHandler))
{
return null;
}
var now = clock.GetCurrentInstant();
var now = clock.GetCurrentInstant();
var eventTime =
@event.Headers.ContainsKey(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() :
now;
var eventTime =
@event.Headers.ContainsKey(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() :
now;
var expires = eventTime.Plus(Constants.ExpirationTime);
var expires = eventTime.Plus(Constants.ExpirationTime);
if (expires < now)
{
return null;
}
if (expires < now)
{
return null;
}
if (!triggerHandler.Trigger(typed.Payload, rule.Trigger, ruleId))
{
return null;
}
if (!triggerHandler.Trigger(typed.Payload, rule.Trigger, ruleId))
{
return null;
}
var appEventEnvelope = @event.To<AppEvent>();
var appEventEnvelope = @event.To<AppEvent>();
var enrichedEvent = await triggerHandler.CreateEnrichedEventAsync(appEventEnvelope);
var enrichedEvent = await triggerHandler.CreateEnrichedEventAsync(appEventEnvelope);
if (enrichedEvent == null)
{
return null;
}
if (enrichedEvent == null)
{
return null;
}
await eventEnricher.EnrichAsync(enrichedEvent, typed);
await eventEnricher.EnrichAsync(enrichedEvent, typed);
if (!triggerHandler.Trigger(enrichedEvent, rule.Trigger))
{
return null;
}
if (!triggerHandler.Trigger(enrichedEvent, rule.Trigger))
{
return null;
}
var actionName = typeNameRegistry.GetName(actionType);
var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action);
var actionName = typeNameRegistry.GetName(actionType);
var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action);
var json = jsonSerializer.Serialize(actionData.Data);
var json = jsonSerializer.Serialize(actionData.Data);
var job = new RuleJob
var job = new RuleJob
{
JobId = Guid.NewGuid(),
ActionName = actionName,
ActionData = json,
AppId = enrichedEvent.AppId.Id,
Created = now,
EventName = enrichedEvent.Name,
ExecutionPartition = enrichedEvent.Partition,
Expires = expires,
Description = actionData.Description
};
return job;
}
catch (Exception ex)
{
JobId = Guid.NewGuid(),
ActionName = actionName,
ActionData = json,
AppId = enrichedEvent.AppId.Id,
Created = now,
EventName = enrichedEvent.Name,
ExecutionPartition = enrichedEvent.Partition,
Expires = expires,
Description = actionData.Description
};
return job;
log.LogError(ex, w => w
.WriteProperty("action", "createRuleJob")
.WriteProperty("status", "Failed"));
return null;
}
}
public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job)

2
src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs

@ -132,7 +132,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return eventId.Id == schema.SchemaId;
}
private bool MatchsCondition(ContentChangedTriggerSchemaV2 schema, EnrichedSchemaEvent @event)
private bool MatchsCondition(ContentChangedTriggerSchemaV2 schema, EnrichedSchemaEventBase @event)
{
return string.IsNullOrWhiteSpace(schema.Condition) || scriptEngine.Evaluate("event", @event, schema.Condition);
}

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(SchemaChangedTrigger trigger)
{
return Task.FromResult(Enumerable.Empty<ValidationError>());
}
public Task<IEnumerable<ValidationError>> Visit(UsageTrigger trigger)
{
var errors = new List<ValidationError>();

74
src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs

@ -0,0 +1,74 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules;
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;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Schemas
{
public sealed class SchemaChangedTriggerHandler : RuleTriggerHandler<SchemaChangedTrigger, SchemaEvent, EnrichedSchemaEvent>
{
private readonly IScriptEngine scriptEngine;
public SchemaChangedTriggerHandler(IScriptEngine scriptEngine)
{
Guard.NotNull(scriptEngine, nameof(scriptEngine));
this.scriptEngine = scriptEngine;
}
protected override Task<EnrichedSchemaEvent> CreateEnrichedEventAsync(Envelope<SchemaEvent> @event)
{
var result = new EnrichedSchemaEvent();
SimpleMapper.Map(@event.Payload, result);
switch (@event.Payload)
{
case FieldEvent _:
case SchemaPreviewUrlsConfigured _:
case SchemaScriptsConfigured _:
case SchemaUpdated _:
case ParentFieldEvent _:
result.Type = EnrichedSchemaEventType.Updated;
break;
case SchemaCreated _:
result.Type = EnrichedSchemaEventType.Created;
break;
case SchemaPublished _:
result.Type = EnrichedSchemaEventType.Published;
break;
case SchemaUnpublished _:
result.Type = EnrichedSchemaEventType.Unpublished;
break;
case SchemaDeleted _:
result.Type = EnrichedSchemaEventType.Deleted;
break;
default:
result = null;
break;
}
result.Name = $"Schema{result.Type}";
return Task.FromResult(result);
}
protected override bool Trigger(EnrichedSchemaEvent @event, SchemaChangedTrigger trigger)
{
return string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", @event, trigger.Condition);
}
}
}

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(SchemaChangedTrigger trigger)
{
return SimpleMapper.Map(trigger, new AssetChangedRuleTriggerDto());
}
public RuleTriggerDto Visit(UsageTrigger trigger)
{
return SimpleMapper.Map(trigger, new UsageRuleTriggerDto());

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

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// 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 SchemaChangedRuleTriggerDto : RuleTriggerDto
{
/// <summary>
/// Javascript condition when to trigger.
/// </summary>
public string Condition { get; set; }
public override RuleTrigger ToTrigger()
{
return SimpleMapper.Map(this, new SchemaChangedTrigger());
}
}
}

4
src/Squidex/Config/Domain/RuleServices.cs

@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.UsageTracking;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Extensions.Actions;
using Squidex.Infrastructure.EventSourcing;
@ -29,6 +30,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<SchemaChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<UsageTriggerHandler>()
.As<IRuleTriggerHandler>();

1
src/Squidex/app/features/rules/declarations.ts

@ -19,6 +19,7 @@ export * from './pages/rules/actions/webhook-action.component';
export * from './pages/rules/triggers/asset-changed-trigger.component';
export * from './pages/rules/triggers/content-changed-trigger.component';
export * from './pages/rules/triggers/schema-changed-trigger.component';
export * from './pages/rules/triggers/usage-trigger.component';
export * from './pages/rules/rule-element.component';

2
src/Squidex/app/features/rules/module.ts

@ -30,6 +30,7 @@ import {
RuleEventsPageComponent,
RulesPageComponent,
RuleWizardComponent,
SchemaChangedTriggerComponent,
SlackActionComponent,
TweetActionComponent,
UsageTriggerComponent,
@ -78,6 +79,7 @@ const routes: Routes = [
RuleEventsPageComponent,
RulesPageComponent,
RuleWizardComponent,
SchemaChangedTriggerComponent,
SlackActionComponent,
TweetActionComponent,
UsageTriggerComponent,

7
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -49,6 +49,13 @@
[triggerFormSubmitted]="triggerForm.submitted | async">
</sqx-content-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'SchemaChanged'">
<sqx-schema-changed-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form"
[triggerFormSubmitted]="triggerForm.submitted | async">
</sqx-schema-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'Usage'">
<sqx-usage-trigger
[trigger]="trigger"

28
src/Squidex/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.html

@ -0,0 +1,28 @@
<div [formGroup]="triggerForm" class="form-horizontal">
<div class="form-group">
<label for="condition">Condition</label>
<sqx-control-errors for="condition" [submitted]="triggerFormSubmitted"></sqx-control-errors>
<textarea class="form-control code" id="condition" formControlName="condition" placeholder="Optional condition as javascript expression"></textarea>
</div>
<div class="help">
<h4>Conditions</h4>
<p>Conditions are javascript expressions that define when to trigger, for example:</p>
<ul class="help-examples">
<li class="help-example">
Specific events:<br/>
<sqx-code>event.type == 'Created' || event.type == 'Updated'</sqx-code>
</li>
<li class="help-example">
Specific schema only:<br/>
<sqx-code>schemaId.name === 'my-schema'</sqx-code>
</li>
</ul>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.scss

@ -0,0 +1,6 @@
@import '_vars';
@import '_mixins';
textarea {
height: 100px;
}

30
src/Squidex/app/features/rules/pages/rules/triggers/schema-changed-trigger.component.ts

@ -0,0 +1,30 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'sqx-schema-changed-trigger',
styleUrls: ['./schema-changed-trigger.component.scss'],
templateUrl: './schema-changed-trigger.component.html'
})
export class SchemaChangedTriggerComponent implements OnInit {
@Input()
public trigger: any;
@Input()
public triggerForm: FormGroup;
@Input()
public triggerFormSubmitted = false;
public ngOnInit() {
this.triggerForm.setControl('condition',
new FormControl(this.trigger.condition || ''));
}
}

4
src/Squidex/app/framework/angular/modals/modal-target.directive.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { timer } from 'rxjs';
import { ResourceOwner } from '@app/framework/internal';
@ -23,7 +23,7 @@ const POSITION_FULL = 'full';
@Directive({
selector: '[sqxModalTarget]'
})
export class ModalTargetDirective extends ResourceOwner implements AfterViewInit, OnInit {
export class ModalTargetDirective extends ResourceOwner implements AfterViewInit, OnDestroy, OnInit {
private targetElement: any;
@Input('sqxModalTarget')

11
src/Squidex/app/framework/angular/modals/tooltip.component.ts

@ -10,7 +10,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, R
import {
fadeAnimation,
ModalModel,
StatefulComponent
ResourceOwner
} from '@app/framework/internal';
@Component({
@ -22,7 +22,7 @@ import {
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipComponent extends StatefulComponent implements OnInit {
export class TooltipComponent extends ResourceOwner implements OnInit {
@Input()
public target: any;
@ -31,10 +31,11 @@ export class TooltipComponent extends StatefulComponent implements OnInit {
public modal = new ModalModel();
constructor(changeDetector: ChangeDetectorRef,
constructor(
private readonly changeDetector: ChangeDetectorRef,
private readonly renderer: Renderer2
) {
super(changeDetector, {});
super();
}
public ngOnInit() {
@ -42,6 +43,8 @@ export class TooltipComponent extends StatefulComponent implements OnInit {
this.own(
this.renderer.listen(this.target, 'mouseenter', () => {
this.modal.show();
this.changeDetector.detectChanges();
}));
this.own(

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

@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Xunit;
#pragma warning disable xUnit2009 // Do not use boolean check to check for string equality
@ -80,7 +81,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.TriggerType)
.Returns(typeof(ContentChangedTriggerV2));
sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, eventEnricher, TestUtils.DefaultSerializer, clock, typeNameRegistry);
var log = A.Fake<ISemanticLog>();
sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, eventEnricher, TestUtils.DefaultSerializer, clock, log, typeNameRegistry);
}
[Fact]

148
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs

@ -0,0 +1,148 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.HandleRules;
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;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure.EventSourcing;
using Xunit;
#pragma warning disable SA1401 // Fields must be private
namespace Squidex.Domain.Apps.Entities.Schemas
{
public class SchemaChangedTriggerHandlerTests
{
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IRuleTriggerHandler sut;
public SchemaChangedTriggerHandlerTests()
{
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, "true"))
.Returns(true);
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, "false"))
.Returns(false);
sut = new SchemaChangedTriggerHandler(scriptEngine);
}
public static IEnumerable<object[]> TestEvents = new[]
{
new object[] { new SchemaCreated(), EnrichedSchemaEventType.Created },
new object[] { new SchemaUpdated(), EnrichedSchemaEventType.Updated },
new object[] { new SchemaDeleted(), EnrichedSchemaEventType.Deleted },
new object[] { new SchemaPublished(), EnrichedSchemaEventType.Published },
new object[] { new SchemaUnpublished(), EnrichedSchemaEventType.Unpublished }
};
[Theory]
[MemberData(nameof(TestEvents))]
public async Task Should_enrich_events(SchemaEvent @event, EnrichedSchemaEventType type)
{
var envelope = Envelope.Create<AppEvent>(@event).SetEventStreamNumber(12);
var schemaGrain = A.Fake<ISchemaGrain>();
var result = await sut.CreateEnrichedEventAsync(envelope);
Assert.Equal(type, ((EnrichedSchemaEvent)result).Type);
}
[Fact]
public void Should_not_trigger_precheck_when_event_type_not_correct()
{
TestForCondition(string.Empty, trigger =>
{
var result = sut.Trigger(new AppCreated(), trigger, Guid.NewGuid());
Assert.False(result);
});
}
[Fact]
public void Should_trigger_precheck_when_event_type_correct()
{
TestForCondition(string.Empty, trigger =>
{
var result = sut.Trigger(new SchemaCreated(), trigger, Guid.NewGuid());
Assert.True(result);
});
}
[Fact]
public void Should_not_trigger_check_when_event_type_not_correct()
{
TestForCondition(string.Empty, trigger =>
{
var result = sut.Trigger(new EnrichedContentEvent(), trigger);
Assert.False(result);
});
}
[Fact]
public void Should_trigger_check_when_condition_is_empty()
{
TestForCondition(string.Empty, trigger =>
{
var result = sut.Trigger(new EnrichedSchemaEvent(), trigger);
Assert.True(result);
});
}
[Fact]
public void Should_trigger_check_when_condition_matchs()
{
TestForCondition("true", trigger =>
{
var result = sut.Trigger(new EnrichedSchemaEvent(), trigger);
Assert.True(result);
});
}
[Fact]
public void Should_not_trigger_check_when_condition_does_not_matchs()
{
TestForCondition("false", trigger =>
{
var result = sut.Trigger(new EnrichedSchemaEvent(), trigger);
Assert.False(result);
});
}
private void TestForCondition(string condition, Action<SchemaChangedTrigger> action)
{
var trigger = new SchemaChangedTrigger { Condition = condition };
action(trigger);
if (string.IsNullOrWhiteSpace(condition))
{
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, condition))
.MustNotHaveHappened();
}
else
{
A.CallTo(() => scriptEngine.Evaluate("event", A<object>.Ignored, condition))
.MustHaveHappened();
}
}
}
}
Loading…
Cancel
Save