Browse Source

Introduced enriched events.

pull/306/head
Sebastian 8 years ago
parent
commit
4ed12e1a00
  1. 71
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs
  2. 8
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs
  3. 116
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs
  4. 23
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs
  5. 8
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs
  6. 14
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs
  7. 18
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  8. 33
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs
  9. 19
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventAction.cs
  10. 26
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs
  11. 17
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs
  12. 19
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IEventEnricher.cs
  13. 5
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs
  14. 9
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  15. 77
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  16. 53
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  17. 102
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  18. 17
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs

71
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs

@ -10,12 +10,9 @@ using System.Threading.Tasks;
using Algolia.Search; using Algolia.Search;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1649 // File name must match first type name
@ -24,6 +21,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class AlgoliaJob public sealed class AlgoliaJob
{ {
public string AppId { get; set; } public string AppId { get; set; }
public string ApiKey { get; set; } public string ApiKey { get; set; }
public string ContentId { get; set; } public string ContentId { get; set; }
@ -54,11 +52,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
}); });
} }
protected override async Task<(string Description, AlgoliaJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, AlgoliaAction action) protected override async Task<(string Description, AlgoliaJob Data)> CreateJobAsync(EnrichedEvent @event, AlgoliaAction action)
{ {
if (@event.Payload is ContentEvent contentEvent) if (@event is EnrichedContentEvent contentEvent)
{ {
var contentId = contentEvent.ContentId.ToString(); var contentId = contentEvent.Id.ToString();
var ruleDescription = string.Empty; var ruleDescription = string.Empty;
var ruleJob = new AlgoliaJob var ruleJob = new AlgoliaJob
@ -69,56 +67,17 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
IndexName = await formatter.FormatStringAsync(action.IndexName, @event) IndexName = await formatter.FormatStringAsync(action.IndexName, @event)
}; };
var timestamp = @event.Headers.Timestamp().ToString(); if (contentEvent.Action == EnrichedContentEventAction.Deleted ||
contentEvent.Action == EnrichedContentEventAction.Archived)
switch (@event.Payload) {
ruleDescription = $"Delete entry from Algolia index: {action.IndexName}";
}
else
{ {
case ContentCreated created: ruleDescription = $"Add entry to Algolia index: {action.IndexName}";
{
ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; ruleJob.Content = formatter.ToPayload(contentEvent);
ruleJob.Content["objectID"] = contentId;
ruleJob.Content = new JObject(
new JProperty("objectID", contentId),
new JProperty("id", contentId),
new JProperty("created", timestamp),
new JProperty("createdBy", created.Actor.ToString()),
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", created.Actor.ToString()),
new JProperty("status", Status.Draft.ToString()),
new JProperty("data", formatter.ToRouteData(created.Data)));
break;
}
case ContentUpdated updated:
{
ruleDescription = $"Update entry in Algolia index: {action.IndexName}";
ruleJob.Content = new JObject(
new JProperty("objectID", contentId),
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", updated.Actor.ToString()),
new JProperty("data", formatter.ToRouteData(updated.Data)));
break;
}
case ContentStatusChanged statusChanged:
{
ruleDescription = $"Update entry in Algolia index: {action.IndexName}";
ruleJob.Content = new JObject(
new JProperty("objectID", contentId),
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", statusChanged.Actor.ToString()),
new JProperty("status", statusChanged.Status.ToString()));
break;
}
case ContentDeleted deleted:
{
ruleDescription = $"Delete entry from Algolia index: {action.IndexName}";
break;
}
} }
return (ruleDescription, ruleJob); return (ruleDescription, ruleJob);

8
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs

@ -11,10 +11,9 @@ using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue; using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1649 // File name must match first type name
@ -23,6 +22,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class AzureQueueJob public sealed class AzureQueueJob
{ {
public string QueueConnectionString { get; set; } public string QueueConnectionString { get; set; }
public string QueueName { get; set; } public string QueueName { get; set; }
public string MessageBodyV2 { get; set; } public string MessageBodyV2 { get; set; }
@ -60,9 +60,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
}); });
} }
protected override async Task<(string Description, AzureQueueJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, AzureQueueAction action) protected override async Task<(string Description, AzureQueueJob Data)> CreateJobAsync(EnrichedEvent @event, AzureQueueAction action)
{ {
var body = formatter.ToRouteData(@event, eventName).ToString(Formatting.Indented); var body = formatter.ToEnvelope(@event).ToString(Formatting.Indented);
var queueName = await formatter.FormatStringAsync(action.Queue, @event); var queueName = await formatter.FormatStringAsync(action.Queue, @event);

116
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs

@ -9,12 +9,9 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Elasticsearch.Net; using Elasticsearch.Net;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1649 // File name must match first type name
@ -25,14 +22,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public string Host { get; set; } public string Host { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string Password { get; set; } public string Password { get; set; }
public string ContentId { get; set; } public string ContentId { get; set; }
public string IndexName { get; set; } public string IndexName { get; set; }
public string IndexType { get; set; }
public string Operation { get; set; } public string IndexType { get; set; }
public JObject Content { get; set; } public JObject Content { get; set; }
} }
@ -63,11 +60,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
}); });
} }
protected override async Task<(string Description, ElasticSearchJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, ElasticSearchAction action) protected override async Task<(string Description, ElasticSearchJob Data)> CreateJobAsync(EnrichedEvent @event, ElasticSearchAction action)
{ {
if (@event.Payload is ContentEvent contentEvent) if (@event is EnrichedContentEvent contentEvent)
{ {
var contentId = contentEvent.ContentId.ToString(); var contentId = contentEvent.Id.ToString();
var ruleDescription = string.Empty; var ruleDescription = string.Empty;
var ruleJob = new ElasticSearchJob var ruleJob = new ElasticSearchJob
@ -80,59 +77,17 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
IndexType = await formatter.FormatStringAsync(action.IndexType, @event), IndexType = await formatter.FormatStringAsync(action.IndexType, @event),
}; };
var timestamp = @event.Headers.Timestamp().ToString(); if (contentEvent.Action == EnrichedContentEventAction.Deleted ||
contentEvent.Action == EnrichedContentEventAction.Archived)
var actor = @event.Payload.Actor.ToString();
switch (@event.Payload)
{ {
case ContentCreated created: ruleDescription = $"Delete entry from Algolia index: {action.IndexName}";
{ }
ruleDescription = $"Add entry to ES index: {action.IndexName}"; else
{
ruleJob.Operation = "Create"; ruleDescription = $"Upsert to ES index: {action.IndexName}";
ruleJob.Content = new JObject(
new JProperty("id", contentId), ruleJob.Content = formatter.ToPayload(contentEvent);
new JProperty("created", timestamp), ruleJob.Content["objectID"] = contentId;
new JProperty("createdBy", actor),
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", actor),
new JProperty("status", Status.Draft.ToString()),
new JProperty("data", formatter.ToRouteData(created.Data)));
break;
}
case ContentUpdated updated:
{
ruleDescription = $"Update entry in ES index: {action.IndexName}";
ruleJob.Operation = "Update";
ruleJob.Content = new JObject(
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", actor),
new JProperty("data", formatter.ToRouteData(updated.Data)));
break;
}
case ContentStatusChanged statusChanged:
{
ruleDescription = $"Update entry in ES index: {action.IndexName}";
ruleJob.Operation = "Update";
ruleJob.Content = new JObject(
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", actor),
new JProperty("status", statusChanged.Status.ToString()));
break;
}
case ContentDeleted deleted:
{
ruleDescription = $"Delete entry from ES index: {action.IndexName}";
ruleJob.Operation = "Delete";
break;
}
} }
} }
@ -141,44 +96,23 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(ElasticSearchJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(ElasticSearchJob job)
{ {
if (string.IsNullOrWhiteSpace(job.Operation))
{
return (null, new InvalidOperationException("The action cannot handle this event."));
}
var client = clients.GetClient((new Uri(job.Host, UriKind.Absolute), job.Username, job.Password)); var client = clients.GetClient((new Uri(job.Host, UriKind.Absolute), job.Username, job.Password));
try try
{ {
switch (job.Operation) if (job.Content != null)
{ {
case "Create": var doc = job.Content.ToString();
{
var doc = job.Content.ToString();
var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
return (response.Body, response.OriginalException);
}
case "Update":
{
var doc = new JObject(new JProperty("doc", job.Content)).ToString();
var response = await client.UpdateAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc); var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
return (response.Body, response.OriginalException); return (response.Body, response.OriginalException);
} }
else
case "Delete": {
{ var response = await client.DeleteAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId);
var response = await client.DeleteAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId);
return (response.Body, response.OriginalException);
}
default: return (response.Body, response.OriginalException);
return (null, null);
} }
} }
catch (ElasticsearchClientException ex) catch (ElasticsearchClientException ex)

23
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs

@ -8,9 +8,8 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
#pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1649 // File name must match first type name
@ -28,23 +27,17 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class FastlyActionHandler : RuleActionHandler<FastlyAction, FastlyJob> public sealed class FastlyActionHandler : RuleActionHandler<FastlyAction, FastlyJob>
{ {
private const string Description = "Purge key in fastly"; private const string Description = "Purge key in fastly";
private const string DescriptionIgnore = "Ignore";
protected override Task<(string Description, FastlyJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, FastlyAction action) protected override Task<(string Description, FastlyJob Data)> CreateJobAsync(EnrichedEvent @event, FastlyAction action)
{ {
if (@event.Headers.Contains(CommonHeaders.AggregateId)) var ruleJob = new FastlyJob
{ {
var ruleJob = new FastlyJob Key = @event.AggregateId.ToString(),
{ FastlyApiKey = action.ApiKey,
Key = @event.Headers.AggregateId().ToString(), FastlyServiceID = action.ServiceId
FastlyApiKey = action.ApiKey, };
FastlyServiceID = action.ServiceId
};
return Task.FromResult((Description, ruleJob));
}
return Task.FromResult((DescriptionIgnore, new FastlyJob())); return Task.FromResult((Description, ruleJob));
} }
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job)

8
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs

@ -13,10 +13,9 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
namespace Squidex.Domain.Apps.Core.HandleRules.Actions namespace Squidex.Domain.Apps.Core.HandleRules.Actions
@ -24,6 +23,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class MediumJob public sealed class MediumJob
{ {
public string RequestUrl { get; set; } public string RequestUrl { get; set; }
public string RequestBody { get; set; } public string RequestBody { get; set; }
public string AccessToken { get; set; } public string AccessToken { get; set; }
@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; this.formatter = formatter;
} }
protected override async Task<(string Description, MediumJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, MediumAction action) protected override async Task<(string Description, MediumJob Data)> CreateJobAsync(EnrichedEvent @event, MediumAction action)
{ {
var requestUrl = var requestUrl =
!string.IsNullOrWhiteSpace(action.Author) ? !string.IsNullOrWhiteSpace(action.Author) ?
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
return (Description, ruleJob); return (Description, ruleJob);
} }
private async Task<JArray> ParseTagsAsync(Envelope<AppEvent> @event, MediumAction action) private async Task<JArray> ParseTagsAsync(EnrichedEvent @event, MediumAction action)
{ {
if (string.IsNullOrWhiteSpace(action.Tags)) if (string.IsNullOrWhiteSpace(action.Tags))
{ {

14
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs

@ -11,10 +11,9 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
#pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1649 // File name must match first type name
@ -50,9 +49,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; this.formatter = formatter;
} }
protected override async Task<(string Description, SlackJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, SlackAction action) protected override async Task<(string Description, SlackJob Data)> CreateJobAsync(EnrichedEvent @event, SlackAction action)
{ {
var body = await CreatePayloadAsync(@event, action.Text); var body =
new JObject(
new JProperty("text", await formatter.FormatStringAsync(action.Text, @event)));
var ruleJob = new SlackJob var ruleJob = new SlackJob
{ {
@ -63,11 +64,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
return (Description, ruleJob); return (Description, ruleJob);
} }
private async Task<JObject> CreatePayloadAsync(Envelope<AppEvent> @event, string text)
{
return new JObject(new JProperty("text", await formatter.FormatStringAsync(text, @event)));
}
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(SlackJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(SlackJob job)
{ {
var requestBody = job.Body; var requestBody = job.Body;

18
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs

@ -11,10 +11,9 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
#pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1649 // File name must match first type name
@ -24,7 +23,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class WebhookJob public sealed class WebhookJob
{ {
public string RequestUrl { get; set; } public string RequestUrl { get; set; }
public string RequestSignature { get; set; } public string RequestSignature { get; set; }
public string RequestBodyV2 { get; set; } public string RequestBodyV2 { get; set; }
public JObject RequestBody { get; set; } public JObject RequestBody { get; set; }
@ -49,16 +50,17 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; this.formatter = formatter;
} }
protected override async Task<(string Description, WebhookJob Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, WebhookAction action) protected override async Task<(string Description, WebhookJob Data)> CreateJobAsync(EnrichedEvent @event, WebhookAction action)
{ {
var body = formatter.ToRouteData(@event, eventName).ToString(Formatting.Indented); var requestBody = formatter.ToEnvelope(@event).ToString(Formatting.Indented);
var requestUrl = await formatter.FormatStringAsync(action.Url.ToString(), @event);
var ruleDescription = $"Send event to webhook '{action.Url}'"; var ruleDescription = $"Send event to webhook '{requestUrl}'";
var ruleJob = new WebhookJob var ruleJob = new WebhookJob
{ {
RequestUrl = await formatter.FormatStringAsync(action.Url.ToString(), @event), RequestUrl = requestUrl,
RequestSignature = $"{body}{action.SharedSecret}".Sha256Base64(), RequestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64(),
RequestBodyV2 = body RequestBodyV2 = requestBody
}; };
return (ruleDescription, ruleJob); return (ruleDescription, ruleJob);

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

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// 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 EnrichedContentEventAction Action { 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; }
}
}

19
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventAction.cs

@ -0,0 +1,19 @@
// ==========================================================================
// 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 EnrichedContentEventAction
{
Archived,
Created,
Deleted,
Published,
Restored,
Updated
}
}

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

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public class EnrichedEvent
{
public Guid AggregateId { get; set; }
public NamedId<Guid> AppId { get; set; }
public RefToken Actor { get; set; }
public Instant Timestamp { get; set; }
public string Name { get; set; }
}
}

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

@ -0,0 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public class EnrichedSchemaEvent : EnrichedEvent
{
public NamedId<Guid> SchemaId { get; set; }
}
}

19
src/Squidex.Domain.Apps.Core.Operations/HandleRules/IEventEnricher.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules
{
public interface IEventEnricher
{
Task<EnrichedEvent> EnrichAsync(Envelope<AppEvent> @event);
}
}

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

@ -8,9 +8,8 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
@ -18,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
Type ActionType { get; } Type ActionType { get; }
Task<(string Description, JObject Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, RuleAction action); Task<(string Description, JObject Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action);
Task<(string Dump, Exception Exception)> ExecuteJobAsync(JObject data); Task<(string Dump, Exception Exception)> ExecuteJobAsync(JObject data);
} }

9
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -8,9 +8,8 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
@ -21,9 +20,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TAction); } get { return typeof(TAction); }
} }
async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(Envelope<AppEvent> @event, string eventName, RuleAction action) async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action)
{ {
var (description, data) = await CreateJobAsync(@event, eventName, (TAction)action); var (description, data) = await CreateJobAsync(@event, (TAction)action);
return (description, JObject.FromObject(data)); return (description, JObject.FromObject(data));
} }
@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return await ExecuteJobAsync(typedData); return await ExecuteJobAsync(typedData);
} }
protected abstract Task<(string Description, TData Data)> CreateJobAsync(Envelope<AppEvent> @event, string eventName, TAction action); protected abstract Task<(string Description, TData Data)> CreateJobAsync(EnrichedEvent @event, TAction action);
protected abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(TData job); protected abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(TData job);
} }

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

@ -14,11 +14,9 @@ using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
@ -61,20 +59,20 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
} }
public virtual JToken ToRouteData(object value) public virtual JObject ToPayload(object @event)
{ {
return JToken.FromObject(value, serializer); return JObject.FromObject(@event, serializer);
} }
public virtual JToken ToRouteData(Envelope<AppEvent> @event, string eventName) public virtual JObject ToEnvelope(EnrichedEvent @event)
{ {
return new JObject( return new JObject(
new JProperty("type", eventName), new JProperty("type", @event),
new JProperty("payload", JToken.FromObject(@event.Payload, serializer)), new JProperty("payload", ToPayload(@event)),
new JProperty("timestamp", @event.Headers.Timestamp().ToString())); new JProperty("timestamp", @event.Timestamp.ToString()));
} }
public async virtual Task<string> FormatStringAsync(string text, Envelope<AppEvent> @event) public async virtual Task<string> FormatStringAsync(string text, EnrichedEvent @event)
{ {
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
@ -83,57 +81,46 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var sb = new StringBuilder(text); var sb = new StringBuilder(text);
if (@event.Headers.Contains(CommonHeaders.Timestamp)) sb.Replace(TimestampDateTimePlaceholder, @event.Timestamp.ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture));
{ sb.Replace(TimestampDatePlaceholder, @event.Timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture));
var timestamp = @event.Headers.Timestamp().ToDateTimeUtc();
sb.Replace(TimestampDateTimePlaceholder, timestamp.ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture));
sb.Replace(TimestampDatePlaceholder, timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture));
}
if (@event.Payload.AppId != null) if (@event.AppId != null)
{ {
sb.Replace(AppIdPlaceholder, @event.Payload.AppId.Id.ToString()); sb.Replace(AppIdPlaceholder, @event.AppId.Id.ToString());
sb.Replace(AppNamePlaceholder, @event.Payload.AppId.Name); sb.Replace(AppNamePlaceholder, @event.AppId.Name);
} }
if (@event.Payload is SchemaEvent schemaEvent && schemaEvent.SchemaId != null) if (@event is EnrichedSchemaEvent schemaEvent && schemaEvent.SchemaId != null)
{ {
sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString()); sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString());
sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name); sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name);
} }
if (@event.Payload is ContentEvent contentEvent) if (@event is EnrichedContentEvent contentEvent)
{ {
sb.Replace(ContentUrlPlaceholder, urlGenerator.GenerateContentUIUrl(@event.Payload.AppId, contentEvent.SchemaId, contentEvent.ContentId)); sb.Replace(ContentUrlPlaceholder, urlGenerator.GenerateContentUIUrl(@event.AppId, contentEvent.SchemaId, contentEvent.Id));
sb.Replace(ContentActionPlaceholder, contentEvent.Action.ToString());
} }
await FormatUserInfoAsync(@event, sb); await FormatUserInfoAsync(@event, sb);
FormatContentAction(@event, sb);
var result = sb.ToString(); var result = sb.ToString();
if (@event.Payload is ContentCreated contentCreated && contentCreated.Data != null) if (@event is EnrichedContentEvent contentEvent2)
{ {
result = ReplaceData(contentCreated.Data, result); result = ReplaceData(contentEvent2.Data, result);
}
if (@event.Payload is ContentUpdated contentUpdated && contentUpdated.Data != null)
{
result = ReplaceData(contentUpdated.Data, result);
} }
return result; return result;
} }
private async Task FormatUserInfoAsync(Envelope<AppEvent> @event, StringBuilder sb) private async Task FormatUserInfoAsync(EnrichedEvent @event, StringBuilder sb)
{ {
var text = sb.ToString(); var text = sb.ToString();
if (text.Contains(UserEmailPlaceholder) || text.Contains(UserNamePlaceholder)) if (text.Contains(UserEmailPlaceholder) || text.Contains(UserNamePlaceholder))
{ {
var actor = @event.Payload.Actor; var actor = @event.Actor;
if (actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase)) if (actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase))
{ {
@ -160,28 +147,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
} }
} }
private static void FormatContentAction(Envelope<AppEvent> @event, StringBuilder sb)
{
switch (@event.Payload)
{
case ContentCreated contentCreated:
sb.Replace(ContentActionPlaceholder, "created");
break;
case ContentUpdated contentUpdated:
sb.Replace(ContentActionPlaceholder, "updated");
break;
case ContentStatusChanged contentStatusChanged:
sb.Replace(ContentActionPlaceholder, $"set to {contentStatusChanged.Status.ToString().ToLowerInvariant()}");
break;
case ContentDeleted contentDeleted:
sb.Replace(ContentActionPlaceholder, "deleted");
break;
}
}
private static string ReplaceData(NamedContentData data, string text) private static string ReplaceData(NamedContentData data, string text)
{ {
text = ContentDataPlaceholder.Replace(text, match => text = ContentDataPlaceholder.Replace(text, match =>

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

@ -26,17 +26,20 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private readonly Dictionary<Type, IRuleActionHandler> ruleActionHandlers; private readonly Dictionary<Type, IRuleActionHandler> ruleActionHandlers;
private readonly Dictionary<Type, IRuleTriggerHandler> ruleTriggerHandlers; private readonly Dictionary<Type, IRuleTriggerHandler> ruleTriggerHandlers;
private readonly TypeNameRegistry typeNameRegistry; private readonly TypeNameRegistry typeNameRegistry;
private readonly IEventEnricher eventEnricher;
private readonly IClock clock; private readonly IClock clock;
public RuleService( public RuleService(
IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers, IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers,
IEnumerable<IRuleActionHandler> ruleActionHandlers, IEnumerable<IRuleActionHandler> ruleActionHandlers,
IEventEnricher eventEnricher,
IClock clock, IClock clock,
TypeNameRegistry typeNameRegistry) TypeNameRegistry typeNameRegistry)
{ {
Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers)); Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers));
Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers)); Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers));
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
Guard.NotNull(eventEnricher, nameof(eventEnricher));
Guard.NotNull(clock, nameof(clock)); Guard.NotNull(clock, nameof(clock));
this.typeNameRegistry = typeNameRegistry; this.typeNameRegistry = typeNameRegistry;
@ -44,6 +47,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.ruleTriggerHandlers = ruleTriggerHandlers.ToDictionary(x => x.TriggerType); this.ruleTriggerHandlers = ruleTriggerHandlers.ToDictionary(x => x.TriggerType);
this.ruleActionHandlers = ruleActionHandlers.ToDictionary(x => x.ActionType); this.ruleActionHandlers = ruleActionHandlers.ToDictionary(x => x.ActionType);
this.eventEnricher = eventEnricher;
this.clock = clock; this.clock = clock;
} }
@ -76,41 +81,38 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return null; return null;
} }
var eventName = CreateEventName(appEvent);
var now = clock.GetCurrentInstant(); var now = clock.GetCurrentInstant();
var actionName = typeNameRegistry.GetName(actionType);
var actionData = await actionHandler.CreateJobAsync(appEventEnvelope, eventName, rule.Action);
var eventTime = var eventTime =
@event.Headers.Contains(CommonHeaders.Timestamp) ? @event.Headers.Contains(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() : @event.Headers.Timestamp() :
now; now;
var aggregateId = var expires = eventTime.Plus(Constants.ExpirationTime);
@event.Headers.Contains(CommonHeaders.AggregateId) ?
@event.Headers.AggregateId() : if (expires < now)
Guid.NewGuid(); {
return null;
}
var enrichedEvent = await eventEnricher.EnrichAsync(appEventEnvelope);
var actionName = typeNameRegistry.GetName(actionType);
var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action);
var job = new RuleJob var job = new RuleJob
{ {
JobId = Guid.NewGuid(), JobId = Guid.NewGuid(),
ActionName = actionName, ActionName = actionName,
ActionData = actionData.Data, ActionData = actionData.Data,
AggregateId = aggregateId, AggregateId = enrichedEvent.AggregateId,
AppId = appEvent.AppId.Id, AppId = appEvent.AppId.Id,
Created = now, Created = now,
EventName = eventName, EventName = enrichedEvent.Name,
Expires = eventTime.Plus(Constants.ExpirationTime), Expires = expires,
Description = actionData.Description Description = actionData.Description
}; };
if (job.Expires < now)
{
return null;
}
return job; return job;
} }
@ -152,22 +154,5 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return (ex.ToString(), RuleResult.Failed, TimeSpan.Zero); return (ex.ToString(), RuleResult.Failed, TimeSpan.Zero);
} }
} }
private string CreateEventName(AppEvent appEvent)
{
var eventName = typeNameRegistry.GetName(appEvent.GetType());
if (appEvent is SchemaEvent schemaEvent)
{
if (eventName.StartsWith(ContentPrefix, StringComparison.Ordinal))
{
eventName = eventName.Substring(ContentPrefix.Length);
}
return $"{schemaEvent.SchemaId.Name.ToPascalCase()}{eventName}";
}
return eventName;
}
} }
} }

102
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -17,10 +17,8 @@ using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
using Squidex.Shared.Users; using Squidex.Shared.Users;
using Xunit; using Xunit;
@ -53,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public void Should_serialize_object_to_json() public void Should_serialize_object_to_json()
{ {
var result = sut.ToRouteData(new { Value = 1 }); var result = sut.ToPayload(new { Value = 1 });
Assert.True(result is JObject); Assert.True(result is JObject);
} }
@ -61,9 +59,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public void Should_create_route_data() public void Should_create_route_data()
{ {
var @event = new ContentCreated { AppId = appId }; var @event = new EnrichedContentEvent { AppId = appId };
var result = sut.ToRouteData(AsEnvelope(@event)); var result = sut.ToPayload(@event);
Assert.True(result is JObject); Assert.True(result is JObject);
} }
@ -71,9 +69,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public void Should_create_route_data_from_event() public void Should_create_route_data_from_event()
{ {
var @event = new ContentCreated { AppId = appId }; var @event = new EnrichedContentEvent { AppId = appId, Name = "MyEventName" };
var result = sut.ToRouteData(AsEnvelope(@event), "MyEventName"); var result = sut.ToPayload(@event);
Assert.Equal("MyEventName", result["type"]); Assert.Equal("MyEventName", result["type"]);
} }
@ -81,9 +79,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_replace_app_information_from_event() public async Task Should_replace_app_information_from_event()
{ {
var @event = new ContentCreated { AppId = appId }; var @event = new EnrichedContentEvent { AppId = appId };
var result = await sut.FormatStringAsync("Name $APP_NAME has id $APP_ID", AsEnvelope(@event)); var result = await sut.FormatStringAsync("Name $APP_NAME has id $APP_ID", @event);
Assert.Equal($"Name my-app has id {appId.Id}", result); Assert.Equal($"Name my-app has id {appId.Id}", result);
} }
@ -91,9 +89,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_replace_schema_information_from_event() public async Task Should_replace_schema_information_from_event()
{ {
var @event = new ContentCreated { SchemaId = schemaId }; var @event = new EnrichedContentEvent { SchemaId = schemaId };
var result = await sut.FormatStringAsync("Name $SCHEMA_NAME has id $SCHEMA_ID", AsEnvelope(@event)); var result = await sut.FormatStringAsync("Name $SCHEMA_NAME has id $SCHEMA_ID", @event);
Assert.Equal($"Name my-schema has id {schemaId.Id}", result); Assert.Equal($"Name my-schema has id {schemaId.Id}", result);
} }
@ -103,7 +101,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var envelope = AsEnvelope(new ContentCreated()).SetTimestamp(Instant.FromDateTimeUtc(now)); var envelope = new EnrichedContentEvent { Timestamp = Instant.FromDateTimeUtc(now) };
var result = await sut.FormatStringAsync("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope); var result = await sut.FormatStringAsync("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope);
@ -116,9 +114,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) A.CallTo(() => userResolver.FindByIdOrEmailAsync("123"))
.Returns(user); .Returns(user);
var @event = new ContentCreated { Actor = new RefToken("subject", "123") }; var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") };
var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event);
Assert.Equal($"From me (me@email.com)", result); Assert.Equal($"From me (me@email.com)", result);
} }
@ -129,9 +127,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) A.CallTo(() => userResolver.FindByIdOrEmailAsync("123"))
.Returns(Task.FromResult<IUser>(null)); .Returns(Task.FromResult<IUser>(null));
var @event = new ContentCreated { Actor = new RefToken("subject", "123") }; var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") };
var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event);
Assert.Equal($"From UNDEFINED (UNDEFINED)", result); Assert.Equal($"From UNDEFINED (UNDEFINED)", result);
} }
@ -142,9 +140,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) A.CallTo(() => userResolver.FindByIdOrEmailAsync("123"))
.Throws(new InvalidOperationException()); .Throws(new InvalidOperationException());
var @event = new ContentCreated { Actor = new RefToken("subject", "123") }; var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") };
var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event);
Assert.Equal($"From UNDEFINED (UNDEFINED)", result); Assert.Equal($"From UNDEFINED (UNDEFINED)", result);
} }
@ -152,9 +150,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_format_email_and_display_name_from_client() public async Task Should_format_email_and_display_name_from_client()
{ {
var @event = new ContentCreated { Actor = new RefToken("client", "android") }; var @event = new EnrichedContentEvent { Actor = new RefToken("client", "android") };
var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", AsEnvelope(@event)); var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event);
Assert.Equal($"From client:android (client:android)", result); Assert.Equal($"From client:android (client:android)", result);
} }
@ -167,9 +165,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => urlGenerator.GenerateContentUIUrl(appId, schemaId, contentId)) A.CallTo(() => urlGenerator.GenerateContentUIUrl(appId, schemaId, contentId))
.Returns(url); .Returns(url);
var @event = new ContentCreated { AppId = appId, ContentId = contentId, SchemaId = schemaId }; var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId };
var result = await sut.FormatStringAsync("Go to $CONTENT_URL", AsEnvelope(@event)); var result = await sut.FormatStringAsync("Go to $CONTENT_URL", @event);
Assert.Equal($"Go to {url}", result); Assert.Equal($"Go to {url}", result);
} }
@ -177,7 +175,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_undefined_when_field_not_found() public async Task Should_return_undefined_when_field_not_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -186,7 +184,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.country.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.country.iv", @event);
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
@ -194,7 +192,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_undefined_when_partition_not_found() public async Task Should_return_undefined_when_partition_not_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -203,7 +201,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de", @event);
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
@ -211,7 +209,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_undefined_when_array_item_not_found() public async Task Should_return_undefined_when_array_item_not_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -220,7 +218,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", new JArray())) .AddValue("iv", new JArray()))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.10", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.10", @event);
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
@ -228,7 +226,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_undefined_when_property_not_found() public async Task Should_return_undefined_when_property_not_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -238,7 +236,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) new JProperty("name", "Berlin"))))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.Name", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.Name", @event);
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
@ -246,7 +244,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_plain_value_when_found() public async Task Should_return_plain_value_when_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -255,7 +253,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event);
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
@ -263,7 +261,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_plain_value_when_found_from_update_event() public async Task Should_return_plain_value_when_found_from_update_event()
{ {
var @event = new ContentUpdated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -272,7 +270,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .AddValue("iv", "Berlin"))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event);
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
@ -280,7 +278,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_undefined_when_null() public async Task Should_return_undefined_when_null()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -289,7 +287,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JValue.CreateNull())) .AddValue("iv", JValue.CreateNull()))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event);
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
@ -297,7 +295,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_undefined_when_undefined() public async Task Should_return_undefined_when_undefined()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -306,7 +304,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JValue.CreateUndefined())) .AddValue("iv", JValue.CreateUndefined()))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event);
Assert.Equal("UNDEFINED", result); Assert.Equal("UNDEFINED", result);
} }
@ -314,7 +312,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_string_when_object() public async Task Should_return_string_when_object()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -324,7 +322,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) new JProperty("name", "Berlin"))))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event);
Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result); Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result);
} }
@ -332,7 +330,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_plain_value_from_array_when_found() public async Task Should_return_plain_value_from_array_when_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -342,7 +340,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
"Berlin"))) "Berlin")))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.0", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.0", @event);
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
@ -350,7 +348,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_return_plain_value_from_object_when_found() public async Task Should_return_plain_value_from_object_when_found()
{ {
var @event = new ContentCreated var @event = new EnrichedContentEvent
{ {
Data = Data =
new NamedContentData() new NamedContentData()
@ -360,7 +358,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) new JProperty("name", "Berlin"))))
}; };
var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.name", AsEnvelope(@event)); var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.name", @event);
Assert.Equal("Berlin", result); Assert.Equal("Berlin", result);
} }
@ -368,16 +366,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact] [Fact]
public async Task Should_format_content_actions_when_found() public async Task Should_format_content_actions_when_found()
{ {
Assert.Equal("created", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentCreated()))); Assert.Equal("created", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Created }));
Assert.Equal("updated", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentUpdated()))); Assert.Equal("updated", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Updated }));
Assert.Equal("deleted", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentDeleted()))); Assert.Equal("deleted", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Deleted }));
Assert.Equal("archived", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Archived }));
Assert.Equal("set to archived", await sut.FormatStringAsync("$CONTENT_ACTION", AsEnvelope(new ContentStatusChanged { Status = Status.Archived })));
}
private static Envelope<AppEvent> AsEnvelope<T>(T @event) where T : AppEvent
{
return Envelope.Create<AppEvent>(@event).To<AppEvent>();
} }
} }
} }

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

@ -11,6 +11,7 @@ using FakeItEasy;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Rules.Triggers;
@ -28,6 +29,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
private readonly IRuleTriggerHandler ruleTriggerHandler = A.Fake<IRuleTriggerHandler>(); private readonly IRuleTriggerHandler ruleTriggerHandler = A.Fake<IRuleTriggerHandler>();
private readonly IRuleActionHandler ruleActionHandler = A.Fake<IRuleActionHandler>(); private readonly IRuleActionHandler ruleActionHandler = A.Fake<IRuleActionHandler>();
private readonly IEventEnricher eventEnricher = A.Fake<IEventEnricher>();
private readonly IClock clock = A.Fake<IClock>(); private readonly IClock clock = A.Fake<IClock>();
private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry(); private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry();
private readonly RuleService sut; private readonly RuleService sut;
@ -57,13 +59,16 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
typeNameRegistry.Map(typeof(ContentCreated)); typeNameRegistry.Map(typeof(ContentCreated));
typeNameRegistry.Map(typeof(WebhookAction)); typeNameRegistry.Map(typeof(WebhookAction));
A.CallTo(() => eventEnricher.EnrichAsync(A<Envelope<AppEvent>>.Ignored))
.Returns(new EnrichedContentEvent());
A.CallTo(() => ruleActionHandler.ActionType) A.CallTo(() => ruleActionHandler.ActionType)
.Returns(typeof(WebhookAction)); .Returns(typeof(WebhookAction));
A.CallTo(() => ruleTriggerHandler.TriggerType) A.CallTo(() => ruleTriggerHandler.TriggerType)
.Returns(typeof(ContentChangedTrigger)); .Returns(typeof(ContentChangedTrigger));
sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, clock, typeNameRegistry); sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, eventEnricher, clock, typeNameRegistry);
} }
[Fact] [Fact]
@ -128,15 +133,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var actionData = new JObject(); var actionData = new JObject();
var actionDescription = "MyDescription"; var actionDescription = "MyDescription";
var eventName = "MySchemaCreatedEvent";
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.Returns(now); .Returns(now);
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleActionHandler.CreateJobAsync(A<Envelope<AppEvent>>.Ignored, eventName, ruleConfig.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(A<EnrichedEvent>.Ignored, ruleConfig.Action))
.Returns((actionDescription, actionData)); .Returns((actionDescription, actionData));
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -160,21 +163,17 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var actionData = new JObject(); var actionData = new JObject();
var actionDescription = "MyDescription"; var actionDescription = "MyDescription";
var eventName = "MySchemaCreatedEvent";
A.CallTo(() => clock.GetCurrentInstant()) A.CallTo(() => clock.GetCurrentInstant())
.Returns(now); .Returns(now);
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger)) A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))
.Returns(true); .Returns(true);
A.CallTo(() => ruleActionHandler.CreateJobAsync(A<Envelope<AppEvent>>.Ignored, eventName, ruleConfig.Action)) A.CallTo(() => ruleActionHandler.CreateJobAsync(A<EnrichedEvent>.Ignored, ruleConfig.Action))
.Returns((actionDescription, actionData)); .Returns((actionDescription, actionData));
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
Assert.Equal(eventName, job.EventName);
Assert.Equal(actionData, job.ActionData); Assert.Equal(actionData, job.ActionData);
Assert.Equal(actionName, job.ActionName); Assert.Equal(actionName, job.ActionName);
Assert.Equal(actionDescription, job.Description); Assert.Equal(actionDescription, job.Description);

Loading…
Cancel
Save