Browse Source

Feature/simulator2 (#756)

* SimulatorV2

* Improvements to rule simulator.

* Increase color contrast.
pull/758/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
b980d55d37
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      backend/i18n/frontend_en.json
  2. 15
      backend/i18n/frontend_it.json
  3. 15
      backend/i18n/frontend_nl.json
  4. 15
      backend/i18n/frontend_zh.json
  5. 15
      backend/i18n/source/frontend_en.json
  6. 71
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs
  7. 18
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  8. 8
      backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs
  9. 30
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs
  10. 20
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs
  11. 22
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/SimulatedRuleEvent.cs
  12. 11
      backend/src/Squidex/Areas/Api/Controllers/Rules/Models/SimulatedRuleEventDto.cs
  13. 81
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  14. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs
  15. 2
      frontend/app/features/rules/pages/simulator/rule-simulator-page.component.html
  16. 75
      frontend/app/features/rules/pages/simulator/simulated-rule-event.component.html
  17. 64
      frontend/app/features/rules/pages/simulator/simulated-rule-event.component.scss
  18. 4
      frontend/app/features/rules/pages/simulator/simulated-rule-event.component.ts
  19. 7
      frontend/app/framework/angular/forms/editors/code-editor.component.ts
  20. 4
      frontend/app/shared/services/rules.service.spec.ts
  21. 4
      frontend/app/shared/services/rules.service.ts

15
backend/i18n/frontend_en.json

@ -661,6 +661,7 @@
"rules.empty": "No rule created yet.", "rules.empty": "No rule created yet.",
"rules.emptyAddRule": "Add Rule", "rules.emptyAddRule": "Add Rule",
"rules.enqueued": "Rule has been added to the queue.", "rules.enqueued": "Rule has been added to the queue.",
"rules.enrichedEvent": "Enriched Event",
"rules.itemPageTitle": "Rule", "rules.itemPageTitle": "Rule",
"rules.listPageTitle": "Rules", "rules.listPageTitle": "Rules",
"rules.loadFailed": "Failed to load Rules. Please reload.", "rules.loadFailed": "Failed to load Rules. Please reload.",
@ -694,6 +695,20 @@
"rules.runRuleConfirmTitle": "Run rule", "rules.runRuleConfirmTitle": "Run rule",
"rules.simulate": "Simulate", "rules.simulate": "Simulate",
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job will be taken from the queue and executed.",
"rules.simulation.conditionEvaluated": "Event is evaluated, whether it matches the conditions in the tigger. ",
"rules.simulation.errorWrongConditionDoesNotMatch": "Condition does not match to the trigger.",
"rules.simulation.errorWrongDisabled": "Rule is dissabled.",
"rules.simulation.errorWrongEventForTrigger": "Event does not match to the trigger.",
"rules.simulation.errorWrongEventMismatch": "Event does not match to the trigger.",
"rules.simulation.errorWrongFailed": "Internal Error.",
"rules.simulation.errorWrongFromRule": "Event has been created from another rule and will be skipped to prevent endless loops.",
"rules.simulation.errorWrongNoAction": "Action type is obsolete and has been removed.",
"rules.simulation.errorWrongNoTrigger": "Trigger type is obsolete and has been removed.",
"rules.simulation.errorWrongTooOld": "Event is too old.",
"rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulator": "Simulator", "rules.simulator": "Simulator",
"rules.stop": "Rule will stop soon.", "rules.stop": "Rule will stop soon.",
"rules.triggerConfirmText": "Do you really want to trigger the rule?", "rules.triggerConfirmText": "Do you really want to trigger the rule?",

15
backend/i18n/frontend_it.json

@ -661,6 +661,7 @@
"rules.empty": "Nessuna regola è stato ancora creata.", "rules.empty": "Nessuna regola è stato ancora creata.",
"rules.emptyAddRule": "Aggiungi una regola", "rules.emptyAddRule": "Aggiungi una regola",
"rules.enqueued": "La regola è stata aggiunta alle code.", "rules.enqueued": "La regola è stata aggiunta alle code.",
"rules.enrichedEvent": "Enriched Event",
"rules.itemPageTitle": "Rule", "rules.itemPageTitle": "Rule",
"rules.listPageTitle": "Regole", "rules.listPageTitle": "Regole",
"rules.loadFailed": "Non è stato possibile caricare le regole. Per favore ricarica.", "rules.loadFailed": "Non è stato possibile caricare le regole. Per favore ricarica.",
@ -694,6 +695,20 @@
"rules.runRuleConfirmTitle": "Esegui la regola", "rules.runRuleConfirmTitle": "Esegui la regola",
"rules.simulate": "Simulate", "rules.simulate": "Simulate",
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job will be taken from the queue and executed.",
"rules.simulation.conditionEvaluated": "Event is evaluated, whether it matches the conditions in the tigger. ",
"rules.simulation.errorWrongConditionDoesNotMatch": "Condition does not match to the trigger.",
"rules.simulation.errorWrongDisabled": "Rule is dissabled.",
"rules.simulation.errorWrongEventForTrigger": "Event does not match to the trigger.",
"rules.simulation.errorWrongEventMismatch": "Event does not match to the trigger.",
"rules.simulation.errorWrongFailed": "Internal Error.",
"rules.simulation.errorWrongFromRule": "Event has been created from another rule and will be skipped to prevent endless loops.",
"rules.simulation.errorWrongNoAction": "Action type is obsolete and has been removed.",
"rules.simulation.errorWrongNoTrigger": "Trigger type is obsolete and has been removed.",
"rules.simulation.errorWrongTooOld": "Event is too old.",
"rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulator": "Simulator", "rules.simulator": "Simulator",
"rules.stop": "La regola si fermerà al più presto.", "rules.stop": "La regola si fermerà al più presto.",
"rules.triggerConfirmText": "Sei sicuro che voler attivare la regola?", "rules.triggerConfirmText": "Sei sicuro che voler attivare la regola?",

15
backend/i18n/frontend_nl.json

@ -661,6 +661,7 @@
"rules.empty": "Nog geen regel aangemaakt.", "rules.empty": "Nog geen regel aangemaakt.",
"rules.emptyAddRule": "Regel toevoegen", "rules.emptyAddRule": "Regel toevoegen",
"rules.enqueued": "Regel is toegevoegd aan de wachtrij.", "rules.enqueued": "Regel is toegevoegd aan de wachtrij.",
"rules.enrichedEvent": "Enriched Event",
"rules.itemPageTitle": "Rule", "rules.itemPageTitle": "Rule",
"rules.listPageTitle": "Regels", "rules.listPageTitle": "Regels",
"rules.loadFailed": "Laden van regels is mislukt. Laad opnieuw.", "rules.loadFailed": "Laden van regels is mislukt. Laad opnieuw.",
@ -694,6 +695,20 @@
"rules.runRuleConfirmTitle": "Regel uitvoeren", "rules.runRuleConfirmTitle": "Regel uitvoeren",
"rules.simulate": "Simulate", "rules.simulate": "Simulate",
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job will be taken from the queue and executed.",
"rules.simulation.conditionEvaluated": "Event is evaluated, whether it matches the conditions in the tigger. ",
"rules.simulation.errorWrongConditionDoesNotMatch": "Condition does not match to the trigger.",
"rules.simulation.errorWrongDisabled": "Rule is dissabled.",
"rules.simulation.errorWrongEventForTrigger": "Event does not match to the trigger.",
"rules.simulation.errorWrongEventMismatch": "Event does not match to the trigger.",
"rules.simulation.errorWrongFailed": "Internal Error.",
"rules.simulation.errorWrongFromRule": "Event has been created from another rule and will be skipped to prevent endless loops.",
"rules.simulation.errorWrongNoAction": "Action type is obsolete and has been removed.",
"rules.simulation.errorWrongNoTrigger": "Trigger type is obsolete and has been removed.",
"rules.simulation.errorWrongTooOld": "Event is too old.",
"rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulator": "Simulator", "rules.simulator": "Simulator",
"rules.stop": "Regel stopt binnenkort.", "rules.stop": "Regel stopt binnenkort.",
"rules.triggerConfirmText": "Wil je echt de regel activeren?", "rules.triggerConfirmText": "Wil je echt de regel activeren?",

15
backend/i18n/frontend_zh.json

@ -661,6 +661,7 @@
"rules.empty": "尚未创建规则。", "rules.empty": "尚未创建规则。",
"rules.emptyAddRule": "添加规则", "rules.emptyAddRule": "添加规则",
"rules.enqueued": "规则已加入队列。", "rules.enqueued": "规则已加入队列。",
"rules.enrichedEvent": "Enriched Event",
"rules.itemPageTitle": "规则", "rules.itemPageTitle": "规则",
"rules.listPageTitle": "规则", "rules.listPageTitle": "规则",
"rules.loadFailed": "加载规则失败。请重新加载。", "rules.loadFailed": "加载规则失败。请重新加载。",
@ -694,6 +695,20 @@
"rules.runRuleConfirmTitle": "运行规则", "rules.runRuleConfirmTitle": "运行规则",
"rules.simulate": "模拟", "rules.simulate": "模拟",
"rules.simulateTooltip": "使用最近 100 个事件模拟此规则。", "rules.simulateTooltip": "使用最近 100 个事件模拟此规则。",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job will be taken from the queue and executed.",
"rules.simulation.conditionEvaluated": "Event is evaluated, whether it matches the conditions in the tigger. ",
"rules.simulation.errorWrongConditionDoesNotMatch": "Condition does not match to the trigger.",
"rules.simulation.errorWrongDisabled": "Rule is dissabled.",
"rules.simulation.errorWrongEventForTrigger": "Event does not match to the trigger.",
"rules.simulation.errorWrongEventMismatch": "Event does not match to the trigger.",
"rules.simulation.errorWrongFailed": "Internal Error.",
"rules.simulation.errorWrongFromRule": "Event has been created from another rule and will be skipped to prevent endless loops.",
"rules.simulation.errorWrongNoAction": "Action type is obsolete and has been removed.",
"rules.simulation.errorWrongNoTrigger": "Trigger type is obsolete and has been removed.",
"rules.simulation.errorWrongTooOld": "Event is too old.",
"rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulator": "模拟器", "rules.simulator": "模拟器",
"rules.stop": "规则很快就会停止。", "rules.stop": "规则很快就会停止。",
"rules.triggerConfirmText": "你真的要触发规则吗?", "rules.triggerConfirmText": "你真的要触发规则吗?",

15
backend/i18n/source/frontend_en.json

@ -661,6 +661,7 @@
"rules.empty": "No rule created yet.", "rules.empty": "No rule created yet.",
"rules.emptyAddRule": "Add Rule", "rules.emptyAddRule": "Add Rule",
"rules.enqueued": "Rule has been added to the queue.", "rules.enqueued": "Rule has been added to the queue.",
"rules.enrichedEvent": "Enriched Event",
"rules.itemPageTitle": "Rule", "rules.itemPageTitle": "Rule",
"rules.listPageTitle": "Rules", "rules.listPageTitle": "Rules",
"rules.loadFailed": "Failed to load Rules. Please reload.", "rules.loadFailed": "Failed to load Rules. Please reload.",
@ -694,6 +695,20 @@
"rules.runRuleConfirmTitle": "Run rule", "rules.runRuleConfirmTitle": "Run rule",
"rules.simulate": "Simulate", "rules.simulate": "Simulate",
"rules.simulateTooltip": "Simulate this rules using the last 100 events.", "rules.simulateTooltip": "Simulate this rules using the last 100 events.",
"rules.simulation.actionCreated": "Job is created from the enriched event and action and added to a job queue.",
"rules.simulation.actionExecuted": "Job will be taken from the queue and executed.",
"rules.simulation.conditionEvaluated": "Event is evaluated, whether it matches the conditions in the tigger. ",
"rules.simulation.errorWrongConditionDoesNotMatch": "Condition does not match to the trigger.",
"rules.simulation.errorWrongDisabled": "Rule is dissabled.",
"rules.simulation.errorWrongEventForTrigger": "Event does not match to the trigger.",
"rules.simulation.errorWrongEventMismatch": "Event does not match to the trigger.",
"rules.simulation.errorWrongFailed": "Internal Error.",
"rules.simulation.errorWrongFromRule": "Event has been created from another rule and will be skipped to prevent endless loops.",
"rules.simulation.errorWrongNoAction": "Action type is obsolete and has been removed.",
"rules.simulation.errorWrongNoTrigger": "Trigger type is obsolete and has been removed.",
"rules.simulation.errorWrongTooOld": "Event is too old.",
"rules.simulation.eventEnriched": "Event is enriched with additional data",
"rules.simulation.eventQueried": "Event is queried from the database",
"rules.simulator": "Simulator", "rules.simulator": "Simulator",
"rules.stop": "Rule will stop soon.", "rules.stop": "Rule will stop soon.",
"rules.triggerConfirmText": "Do you really want to trigger the rule?", "rules.triggerConfirmText": "Do you really want to trigger the rule?",

71
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs

@ -7,20 +7,69 @@
using System; using System;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
public sealed record JobResult(RuleJob? Job, Exception? Exception = null, SkipReason SkipReason = default) public sealed record JobResult
{ {
public static readonly JobResult ConditionDoesNotMatch = new JobResult(null, null, SkipReason.ConditionDoesNotMatch); public static readonly JobResult ConditionDoesNotMatch = new JobResult
public static readonly JobResult Disabled = new JobResult(null, null, SkipReason.Disabled); {
public static readonly JobResult EventMismatch = new JobResult(null, null, SkipReason.EventMismatch); SkipReason = SkipReason.ConditionDoesNotMatch
public static readonly JobResult FromRule = new JobResult(null, null, SkipReason.FromRule); };
public static readonly JobResult NoAction = new JobResult(null, null, SkipReason.NoAction);
public static readonly JobResult NoTrigger = new JobResult(null, null, SkipReason.NoTrigger); public static readonly JobResult Disabled = new JobResult
public static readonly JobResult TooOld = new JobResult(null, null, SkipReason.TooOld); {
public static readonly JobResult WrongEventForTrigger = new JobResult(null, null, SkipReason.WrongEventForTrigger); SkipReason = SkipReason.Disabled
};
public static readonly JobResult EventMismatch = new JobResult
{
SkipReason = SkipReason.EventMismatch
};
public static readonly JobResult FromRule = new JobResult
{
SkipReason = SkipReason.FromRule
};
public static readonly JobResult NoAction = new JobResult
{
SkipReason = SkipReason.NoAction
};
public static readonly JobResult NoTrigger = new JobResult
{
SkipReason = SkipReason.NoTrigger
};
public static readonly JobResult TooOld = new JobResult
{
SkipReason = SkipReason.TooOld
};
public static readonly JobResult WrongEventForTrigger = new JobResult
{
SkipReason = SkipReason.WrongEventForTrigger
};
public RuleJob? Job { get; init; }
public EnrichedEvent? EnrichedEvent { get; init; }
public Exception? Exception { get; init; }
public SkipReason SkipReason { get; init; }
public static JobResult Failed(Exception exception, EnrichedEvent? enrichedEvent = null, RuleJob? job = null)
{
return new JobResult
{
Job = job,
EnrichedEvent = enrichedEvent,
Exception = exception,
SkipReason = SkipReason.Failed
};
}
} }
} }

18
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
} }
catch (Exception ex) catch (Exception ex)
{ {
job = new JobResult(null, ex); job = JobResult.Failed(ex);
} }
yield return job; yield return job;
@ -144,7 +144,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
} }
} }
private async Task AddJobsAsync(List<JobResult> jobs, Envelope<IEvent> @event, RuleContext context, CancellationToken ct) private async Task AddJobsAsync(List<JobResult> jobs, Envelope<IEvent> @event, RuleContext context,
CancellationToken ct)
{ {
try try
{ {
@ -234,7 +235,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
if (jobs.Count == 0) if (jobs.Count == 0)
{ {
jobs.Add(new JobResult(null, ex, SkipReason.Failed)); jobs.Add(new JobResult
{
EnrichedEvent = enrichedEvent,
Exception = ex,
SkipReason = SkipReason.Failed
});
} }
log.LogError(ex, w => w log.LogError(ex, w => w
@ -245,7 +251,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
} }
catch (Exception ex) catch (Exception ex)
{ {
jobs.Add(new JobResult(null, ex, SkipReason.Failed)); jobs.Add(JobResult.Failed(ex));
log.LogError(ex, w => w log.LogError(ex, w => w
.WriteProperty("action", "createRuleJob") .WriteProperty("action", "createRuleJob")
@ -282,13 +288,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules
job.ActionName = actionName; job.ActionName = actionName;
job.Description = description; job.Description = description;
return new JobResult(job, null); return new JobResult { Job = job, EnrichedEvent = enrichedEvent };
} }
catch (Exception ex) catch (Exception ex)
{ {
job.Description = "Failed to create job"; job.Description = "Failed to create job";
return new JobResult(job, ex); return JobResult.Failed(ex, enrichedEvent, job);
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs

@ -59,11 +59,11 @@ namespace Squidex.Domain.Apps.Entities.Rules
var jobs = ruleService.CreateJobsAsync(@event, ruleContext); var jobs = ruleService.CreateJobsAsync(@event, ruleContext);
await foreach (var (job, ex, _) in jobs) await foreach (var job in jobs)
{ {
if (job != null) if (job.Job != null)
{ {
await ruleEventRepository.EnqueueAsync(job, ex); await ruleEventRepository.EnqueueAsync(job.Job, job.Exception);
} }
} }
} }
@ -101,4 +101,4 @@ namespace Squidex.Domain.Apps.Entities.Rules
}); });
} }
} }
} }

30
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs

@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
var context = GetContext(rule); var context = GetContext(rule);
var result = new List<SimulatedRuleEvent>(MaxSimulatedEvents); var simulatedEvents = new List<SimulatedRuleEvent>(MaxSimulatedEvents);
var fromNow = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromDays(7)); var fromNow = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromDays(7));
@ -53,28 +53,30 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
if (@event?.Payload is AppEvent appEvent) if (@event?.Payload is AppEvent appEvent)
{ {
await foreach (var (job, exception, skip) in ruleService.CreateJobsAsync(@event, context, ct)) await foreach (var result in ruleService.CreateJobsAsync(@event, context, ct))
{ {
var name = job?.EventName; var eventName = result.Job?.EventName;
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(eventName))
{ {
name = ruleService.GetName(appEvent); eventName = ruleService.GetName(appEvent);
} }
var simulationResult = new SimulatedRuleEvent( simulatedEvents.Add(new SimulatedRuleEvent
name, {
job?.ActionName, ActionData = result.Job?.ActionData,
job?.ActionData, ActionName = result.Job?.ActionName,
exception?.Message, EnrichedEvent = result.EnrichedEvent,
skip); Error = result.Exception?.Message,
Event = @event.Payload,
result.Add(simulationResult); EventName = eventName,
SkipReason = result.SkipReason
});
} }
} }
} }
return result; return simulatedEvents;
} }
public Task CancelAsync(DomainId appId) public Task CancelAsync(DomainId appId)

20
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -217,22 +217,22 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
var errors = 0; var errors = 0;
await foreach (var (job, ex, _) in ruleService.CreateSnapshotJobsAsync(context, ct)) await foreach (var job in ruleService.CreateSnapshotJobsAsync(context, ct))
{ {
if (job != null) if (job.Job != null)
{ {
await ruleEventRepository.EnqueueAsync(job, ex); await ruleEventRepository.EnqueueAsync(job.Job, job.Exception);
} }
else if (ex != null) else if (job.Exception != null)
{ {
errors++; errors++;
if (errors >= MaxErrors) if (errors >= MaxErrors)
{ {
throw ex; throw job.Exception;
} }
log.LogWarning(ex, w => w log.LogWarning(job.Exception, w => w
.WriteProperty("action", "runRule") .WriteProperty("action", "runRule")
.WriteProperty("status", "failedPartially")); .WriteProperty("status", "failedPartially"));
} }
@ -255,11 +255,11 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
var jobs = ruleService.CreateJobsAsync(@event, context, ct); var jobs = ruleService.CreateJobsAsync(@event, context, ct);
await foreach (var (job, ex, _) in jobs) await foreach (var job in jobs)
{ {
if (job != null) if (job.Job != null)
{ {
await ruleEventRepository.EnqueueAsync(job, ex); await ruleEventRepository.EnqueueAsync(job.Job, job.Exception);
} }
} }
} }

22
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/SimulatedRuleEvent.cs

@ -7,16 +7,22 @@
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Rules.Runner namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
public sealed record SimulatedRuleEvent( public sealed record SimulatedRuleEvent
string EventName,
string? ActionName,
string? ActionData,
string? Error,
SkipReason SkipReason)
{ {
public string EventName { get; init; }
public object Event { get; init; }
public object? EnrichedEvent { get; init; }
public string? ActionName { get; init; }
public string? ActionData { get; init; }
public string? Error { get; init; }
public SkipReason SkipReason { get; init; }
} }
} }

11
backend/src/Squidex/Areas/Api/Controllers/Rules/Models/SimulatedRuleEventDto.cs

@ -20,6 +20,17 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
[Required] [Required]
public string EventName { get; set; } public string EventName { get; set; }
/// <summary>
/// The source event.
/// </summary>
[Required]
public object Event { get; set; }
/// <summary>
/// The enriched event.
/// </summary>
public object? EnrichedEvent { get; set; }
/// <summary> /// <summary>
/// The data for the action. /// The data for the action.
/// </summary> /// </summary>

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

@ -248,9 +248,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new EnrichedContentEvent { AppId = appId } new EnrichedContentEvent { AppId = appId }
}.ToAsyncEnumerable()); }.ToAsyncEnumerable());
var result = await sut.CreateSnapshotJobsAsync(context).ToListAsync(); var jobs = await sut.CreateSnapshotJobsAsync(context).ToListAsync();
Assert.Equal(2, result.Count(x => x.Job != null && x.Exception == null)); Assert.Equal(2, jobs.Count(x => x.Job != null && x.Exception == null));
} }
[Fact] [Fact]
@ -271,9 +271,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new EnrichedContentEvent { AppId = appId } new EnrichedContentEvent { AppId = appId }
}.ToAsyncEnumerable()); }.ToAsyncEnumerable());
var result = await sut.CreateSnapshotJobsAsync(context).ToListAsync(); var jobs = await sut.CreateSnapshotJobsAsync(context).ToListAsync();
Assert.Equal(2, result.Count(x => x.Job == null && x.Exception != null)); Assert.Equal(2, jobs.Count(x => x.Job == null && x.Exception != null));
} }
[Fact] [Fact]
@ -281,9 +281,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
var @event = Envelope.Create(new ContentCreated()); var @event = Envelope.Create(new ContentCreated());
var (_, _, reason) = await sut.CreateJobsAsync(@event, Rule(disable: true)).SingleAsync(); var result = await sut.CreateJobsAsync(@event, Rule(disable: true)).SingleAsync();
Assert.Equal(SkipReason.Disabled, reason); Assert.Equal(SkipReason.Disabled, result.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -294,9 +294,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
var @event = Envelope.Create(new InvalidEvent()); var @event = Envelope.Create(new InvalidEvent());
var (_, _, reason) = await sut.CreateJobsAsync(@event, Rule()).SingleAsync(); var result = await sut.CreateJobsAsync(@event, Rule()).SingleAsync();
Assert.Equal(SkipReason.EventMismatch, reason); Assert.Equal(SkipReason.EventMismatch, result.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -307,9 +307,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
var @event = Envelope.Create(new ContentCreated()); var @event = Envelope.Create(new ContentCreated());
var (_, _, reason) = await sut.CreateJobsAsync(@event, RuleInvalidTrigger()).SingleAsync(); var job = await sut.CreateJobsAsync(@event, RuleInvalidTrigger()).SingleAsync();
Assert.Equal(SkipReason.NoTrigger, reason); Assert.Equal(SkipReason.NoTrigger, job.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -320,9 +320,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
var @event = Envelope.Create(new ContentCreated()); var @event = Envelope.Create(new ContentCreated());
var (_, _, reason) = await sut.CreateJobsAsync(@event, Rule()).SingleAsync(); var job = await sut.CreateJobsAsync(@event, Rule()).SingleAsync();
Assert.Equal(SkipReason.WrongEventForTrigger, reason); Assert.Equal(SkipReason.WrongEventForTrigger, job.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -336,9 +336,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Handles(@event.Payload)) A.CallTo(() => ruleTriggerHandler.Handles(@event.Payload))
.Returns(true); .Returns(true);
var (_, _, reason) = await sut.CreateJobsAsync(@event, RuleInvalidAction()).SingleAsync(); var job = await sut.CreateJobsAsync(@event, RuleInvalidAction()).SingleAsync();
Assert.Equal(SkipReason.NoAction, reason); Assert.Equal(SkipReason.NoAction, job.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -354,9 +354,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Handles(@event.Payload)) A.CallTo(() => ruleTriggerHandler.Handles(@event.Payload))
.Returns(true); .Returns(true);
var (_, _, reason) = await sut.CreateJobsAsync(@event, Rule(ignoreStale: true)).SingleAsync(); var job = await sut.CreateJobsAsync(@event, Rule(ignoreStale: true)).SingleAsync();
Assert.Equal(SkipReason.TooOld, reason); Assert.Equal(SkipReason.TooOld, job.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -395,9 +395,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var @event = Envelope.Create(new ContentCreated { FromRule = true }); var @event = Envelope.Create(new ContentCreated { FromRule = true });
var (_, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
Assert.Equal(SkipReason.FromRule, reason); Assert.Equal(SkipReason.FromRule, job.SkipReason);
A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._)) A.CallTo(() => ruleTriggerHandler.Trigger(A<Envelope<AppEvent>>._, A<RuleContext>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -424,10 +424,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default)) A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default))
.Returns(new List<EnrichedEvent> { enrichedEvent }.ToAsyncEnumerable()); .Returns(new List<EnrichedEvent> { enrichedEvent }.ToAsyncEnumerable());
var (job, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
Assert.Equal(SkipReason.ConditionDoesNotMatch, reason); Assert.Equal(SkipReason.ConditionDoesNotMatch, job.SkipReason);
Assert.NotNull(job); Assert.Equal(enrichedEvent, job.EnrichedEvent);
} }
[Fact] [Fact]
@ -443,9 +443,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Trigger(MatchPayload(@event), context)) A.CallTo(() => ruleTriggerHandler.Trigger(MatchPayload(@event), context))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
var (_, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
Assert.Equal(SkipReason.Failed, reason); Assert.Equal(SkipReason.Failed, job.SkipReason);
} }
[Fact] [Fact]
@ -464,9 +464,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default)) A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default))
.Returns(AsyncEnumerable.Empty<EnrichedEvent>()); .Returns(AsyncEnumerable.Empty<EnrichedEvent>());
var jobs = await sut.CreateJobsAsync(@event, context).ToListAsync(); var job = await sut.CreateJobsAsync(@event, context).ToListAsync();
Assert.Empty(jobs); Assert.Empty(job);
} }
[Fact] [Fact]
@ -490,9 +490,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default)) A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default))
.Returns(new List<EnrichedEvent> { enrichedEvent }.ToAsyncEnumerable()); .Returns(new List<EnrichedEvent> { enrichedEvent }.ToAsyncEnumerable());
var (_, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
Assert.Equal(SkipReason.ConditionDoesNotMatch, reason); Assert.Equal(SkipReason.ConditionDoesNotMatch, job.SkipReason);
Assert.Equal(enrichedEvent, job.EnrichedEvent);
} }
[Fact] [Fact]
@ -515,9 +516,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default)) A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
var (_, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
Assert.Equal(SkipReason.Failed, reason); Assert.Equal(SkipReason.Failed, job.SkipReason);
} }
[Fact] [Fact]
@ -548,9 +549,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent, context.Rule.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent, context.Rule.Action))
.Returns((actionDescription, new ValidData { Value = 10 })); .Returns((actionDescription, new ValidData { Value = 10 }));
var (job, _, _) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
AssertJob(now, enrichedEvent, job!); AssertJob(now, enrichedEvent, job);
A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent, MatchPayload(@event))) A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent, MatchPayload(@event)))
.MustHaveHappened(); .MustHaveHappened();
@ -584,11 +585,12 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent, context.Rule.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent, context.Rule.Action))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
var (job, ex, _) = await sut.CreateJobsAsync(@event, context).SingleAsync(); var job = await sut.CreateJobsAsync(@event, context).SingleAsync();
Assert.NotNull(ex); Assert.NotNull(job.Exception);
Assert.NotNull(job?.ActionData); Assert.NotNull(job.Job?.ActionData);
Assert.NotNull(job?.Description); Assert.NotNull(job.Job?.Description);
Assert.Equal(enrichedEvent, job.EnrichedEvent);
A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent, MatchPayload(@event))) A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent, MatchPayload(@event)))
.MustHaveHappened(); .MustHaveHappened();
@ -631,8 +633,8 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var jobs = await sut.CreateJobsAsync(@event, context, default).ToListAsync(); var jobs = await sut.CreateJobsAsync(@event, context, default).ToListAsync();
AssertJob(now, enrichedEvent1, jobs[0].Job!); AssertJob(now, enrichedEvent1, jobs[0]);
AssertJob(now, enrichedEvent1, jobs[1].Job!); AssertJob(now, enrichedEvent2, jobs[1]);
A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent1, MatchPayload(@event))) A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent1, MatchPayload(@event)))
.MustHaveHappened(); .MustHaveHappened();
@ -741,8 +743,11 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
return A<Envelope<AppEvent>>.That.Matches(x => x.Payload == @event.Payload); return A<Envelope<AppEvent>>.That.Matches(x => x.Payload == @event.Payload);
} }
private void AssertJob(Instant now, EnrichedContentEvent enrichedEvent, RuleJob job) private void AssertJob(Instant now, EnrichedContentEvent enrichedEvent, JobResult result)
{ {
var job = result.Job!;
Assert.Equal(enrichedEvent, result.EnrichedEvent);
Assert.Equal(enrichedEvent.AppId.Id, job.AppId); Assert.Equal(enrichedEvent.AppId.Id, job.AppId);
Assert.Equal(actionData, job.ActionData); Assert.Equal(actionData, job.ActionData);

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs

@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
}; };
A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule.RuleDef), default)) A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule.RuleDef), default))
.Returns(new List<JobResult> { new JobResult(null) }.ToAsyncEnumerable()); .Returns(new List<JobResult> { new JobResult() }.ToAsyncEnumerable());
await sut.EnqueueAsync(rule.RuleDef, rule.Id, @event); await sut.EnqueueAsync(rule.RuleDef, rule.Id, @event);
@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
}; };
A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule.RuleDef), default)) A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule.RuleDef), default))
.Returns(new List<JobResult> { new JobResult(job) }.ToAsyncEnumerable()); .Returns(new List<JobResult> { new JobResult { Job = job } }.ToAsyncEnumerable());
await sut.EnqueueAsync(rule.RuleDef, rule.Id, @event); await sut.EnqueueAsync(rule.RuleDef, rule.Id, @event);
@ -159,7 +159,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
.Returns(new List<IRuleEntity> { rule1, rule2 }); .Returns(new List<IRuleEntity> { rule1, rule2 });
A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule1.RuleDef), default)) A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule1.RuleDef), default))
.Returns(new List<JobResult> { new JobResult(job1) }.ToAsyncEnumerable()); .Returns(new List<JobResult> { new JobResult { Job = job1 } }.ToAsyncEnumerable());
A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule2.RuleDef), default)) A.CallTo(() => ruleService.CreateJobsAsync(@event, A<RuleContext>.That.Matches(x => x.Rule == rule2.RuleDef), default))
.Returns(new List<JobResult>().ToAsyncEnumerable()); .Returns(new List<JobResult>().ToAsyncEnumerable());

2
frontend/app/features/rules/pages/simulator/rule-simulator-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="i18n:rules.ruleSimulator.listPageTitle"></sqx-title> <sqx-title message="i18n:rules.ruleSimulator.listPageTitle"></sqx-title>
<sqx-layout layout="simple" titleText="i18n:rules.simulator" [width]="50"> <sqx-layout layout="main" titleText="i18n:rules.simulator" [width]="50" [hideSidebar]="true">
<ng-container menu> <ng-container menu>
<button type="button" class="btn btn-text-secondary" (click)="simulate()" title="i18n:rules.simulateTooltip"> <button type="button" class="btn btn-text-secondary" (click)="simulate()" title="i18n:rules.simulateTooltip">
<i class="icon-play-line"></i> {{ 'rules.simulate' | sqxTranslate }} <i class="icon-play-line"></i> {{ 'rules.simulate' | sqxTranslate }}

75
frontend/app/features/rules/pages/simulator/simulated-rule-event.component.html

@ -9,7 +9,7 @@
<small class="truncate">{{event.skipReason}}</small> <small class="truncate">{{event.skipReason}}</small>
</td> </td>
<td class="cell-actions"> <td class="cell-actions">
<button type="button" class="btn btn-outline-secondary btn-expand" [class.expanded]="expanded" [disabled]="!event.error && !event.actionData" (click)="expandedChange.emit()"> <button type="button" class="btn btn-outline-secondary btn-expand" [class.expanded]="expanded" (click)="expandedChange.emit()">
<i class="icon-settings"></i> <i class="icon-settings"></i>
</button> </button>
</td> </td>
@ -20,17 +20,72 @@
<h4>{{ 'common.details' | sqxTranslate }}</h4> <h4>{{ 'common.details' | sqxTranslate }}</h4>
</div> </div>
<div class="row event-dump"> <div class="event-dump">
<div class="form-group" *ngIf="event.actionData"> <div class="history">
<label>{{ 'rules.actionData' | sqxTranslate }}</label> <div class="history-start"></div>
<sqx-code-editor [ngModel]="data" [disabled]="true" [wordWrap]="true" height="auto"></sqx-code-editor> <div>
</div> <div class="history-transition">
{{ 'rules.simulation.eventQueried' | sqxTranslate }}
</div>
<div class="history-state" *ngIf="event.event">
<label>{{ 'common.event' | sqxTranslate }}</label>
<sqx-code-editor [ngModel]="event.event" valueMode="Json" [disabled]="true" [wordWrap]="false" height="auto" [maxLines]="20"></sqx-code-editor>
</div>
<ng-container *ngIf="event.enrichedEvent; else error1">
<div class="history-transition">
{{ 'rules.simulation.eventEnriched' | sqxTranslate }}
</div>
<div class="form-group" *ngIf="event.error"> <div class="history-state" *ngIf="event.enrichedEvent">
<label>{{ 'common.error' | sqxTranslate }}</label> <label>{{ 'rules.enrichedEvent' | sqxTranslate }}</label>
<sqx-code-editor [ngModel]="event.enrichedEvent" valueMode="Json" [disabled]="true" [wordWrap]="false" height="auto" [maxLines]="20"></sqx-code-editor>
</div>
<ng-container *ngIf="event.enrichedEvent; else error2">
<div class="history-transition">
{{ 'rules.simulation.conditionEvaluated' | sqxTranslate }}
</div>
<div class="history-transition">
{{ 'rules.simulation.actionCreated' | sqxTranslate }}
</div>
<div class="history-state" *ngIf="event.actionData">
<label>{{ 'rules.actionData' | sqxTranslate }}</label>
<sqx-code-editor [ngModel]="data" [disabled]="true" [wordWrap]="true" height="auto" [maxLines]="20"></sqx-code-editor>
</div>
<div class="history-transition">
{{ 'rules.simulation.actionExecuted' | sqxTranslate }}
</div>
</ng-container>
<ng-template #error2>
<div class="history-transition text-danger">
{{ errorText | sqxTranslate }}
</div>
</ng-template>
</ng-container>
<ng-template #error1>
<div class="history-transition text-danger">
{{ errorText | sqxTranslate }}
</div>
</ng-template>
<sqx-code-editor [ngModel]="event.error" [disabled]="true" [wordWrap]="true" height="auto"></sqx-code-editor> <div class="history-state" *ngIf="event.error">
<label>{{ 'common.error' | sqxTranslate }}</label>
<sqx-code-editor [ngModel]="event.error" [disabled]="true" [wordWrap]="true" height="auto" [maxLines]="20"></sqx-code-editor>
</div>
</div>
<div class="history-end"></div>
</div> </div>
</div> </div>
</td> </td>

64
frontend/app/features/rules/pages/simulator/simulated-rule-event.component.scss

@ -35,4 +35,68 @@ td {
margin: 0; margin: 0;
} }
} }
}
$history-dot-size: 10px;
$history-dot-offset-x: -($history-dot-size * .5 + 1px);
$history-dot-sm-size: 6px;
$history-dot-sm-offset-x: -($history-dot-sm-size * .5 + 1px);
.history {
border-left: 2px dashed $color-border;
margin: .5rem 0;
padding: .5rem 1.5rem;
padding-right: 0;
position: relative;
&-state {
position: relative;
&::before {
@include circle($history-dot-size);
@include absolute(.5rem, null, null, -1.5rem);
background: $color-border;
content: '';
margin: 0;
margin-left: $history-dot-offset-x;
}
}
&-transition {
font-size: 85%;
font-weight: normal;
margin: 1rem 0;
position: relative;
&::before {
@include circle($history-dot-sm-size);
@include absolute(.5rem, null, null, -1.5rem);
background: $color-border;
content: '';
margin: 0;
margin-left: $history-dot-sm-offset-x;
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
&-start,
&-end {
@include circle($history-dot-size);
background: $color-border;
}
&-start {
@include absolute(-$history-dot-size * .5, null, null, $history-dot-offset-x);
}
&-end {
@include absolute(null, null, -$history-dot-size * .5, $history-dot-offset-x);
}
} }

4
frontend/app/features/rules/pages/simulator/simulated-rule-event.component.ts

@ -38,6 +38,10 @@ export class SimulatedRuleEventComponent {
return result; return result;
} }
public get errorText() {
return `rules.simulation.error${this.event.skipReason}`;
}
public get status() { public get status() {
if (this.event.error) { if (this.event.error) {
return 'Failed'; return 'Failed';

7
frontend/app/framework/angular/forms/editors/code-editor.component.ts

@ -49,6 +49,9 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
@Input() @Input()
public valueMode: 'String' | 'Json' = 'String'; public valueMode: 'String' | 'Json' = 'String';
@Input()
public maxLines: number | undefined;
@Input() @Input()
public wordWrap: boolean; public wordWrap: boolean;
@ -80,7 +83,7 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
this.setMode(); this.setMode();
} }
if (changes['height']) { if (changes['height'] || changes['maxLines']) {
this.setHeight(); this.setHeight();
} }
@ -246,7 +249,7 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, string> im
} else if (this.height === 'auto') { } else if (this.height === 'auto') {
this.aceEditor.setOptions({ this.aceEditor.setOptions({
minLines: 3, minLines: 3,
maxLines: 500, maxLines: this.maxLines || 500,
}); });
} }
} }

4
frontend/app/shared/services/rules.service.spec.ts

@ -416,6 +416,8 @@ describe('RulesService', () => {
return { return {
eventName: `name${key}`, eventName: `name${key}`,
event: { value: 'simple' },
enrichedEvent: { value: 'enriched' },
actionName: `action-name${key}`, actionName: `action-name${key}`,
actionData: `action-data${key}`, actionData: `action-data${key}`,
error: `error${key}`, error: `error${key}`,
@ -479,6 +481,8 @@ export function createSimulatedRuleEvent(id: number, suffix = '') {
return new SimulatedRuleEventDto({}, return new SimulatedRuleEventDto({},
`name${key}`, `name${key}`,
{ value: 'simple' },
{ value: 'enriched' },
`action-name${key}`, `action-name${key}`,
`action-data${key}`, `action-data${key}`,
`error${key}`, `error${key}`,

4
frontend/app/shared/services/rules.service.ts

@ -199,6 +199,8 @@ export class SimulatedRuleEventDto {
constructor(links: ResourceLinks, constructor(links: ResourceLinks,
public readonly eventName: string, public readonly eventName: string,
public readonly event: any,
public readonly enrichedEvent: any | undefined,
public readonly actionName: string | undefined, public readonly actionName: string | undefined,
public readonly actionData: string | undefined, public readonly actionData: string | undefined,
public readonly error: string | undefined, public readonly error: string | undefined,
@ -451,6 +453,8 @@ function parseRuleEvent(response: any) {
function parseSimulatedRuleEvent(response: any) { function parseSimulatedRuleEvent(response: any) {
return new SimulatedRuleEventDto(response._links, return new SimulatedRuleEventDto(response._links,
response.eventName, response.eventName,
response.event,
response.enrichedEvent,
response.actionName, response.actionName,
response.actionData, response.actionData,
response.error, response.error,

Loading…
Cancel
Save