Browse Source

Rule event formatter for simple templates.

pull/237/head
Sebastian Stehle 9 years ago
parent
commit
8190486c46
  1. 3
      Squidex.sln.DotSettings
  2. 14
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs
  3. 11
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  4. 137
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  5. 2
      src/Squidex.Domain.Apps.Events/SquidexEvent.cs
  6. 246
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

3
Squidex.sln.DotSettings

@ -44,4 +44,7 @@
All rights reserved. Licensed under the MIT license.
==========================================================================
</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

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

@ -24,13 +24,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
private const string SchemaNamePlaceholder = "$SCHEMA_NAME";
private readonly ConcurrentDictionary<(string AppId, string ApiKey, string IndexName), Index> clients = new ConcurrentDictionary<(string AppId, string ApiKey, string IndexName), Index>();
private readonly JsonSerializer serializer;
private readonly RuleEventFormatter formatter;
public AlgoliaActionHandler(JsonSerializer serializer)
public AlgoliaActionHandler(RuleEventFormatter formatter)
{
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(formatter, nameof(formatter));
this.serializer = serializer;
this.formatter = formatter;
}
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, AlgoliaAction action)
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
ruleData["ContentId"] = contentEvent.ContentId.ToString();
ruleData["Operation"] = "Upsert";
ruleData["IndexName"] = action.IndexName.Replace(SchemaNamePlaceholder, contentEvent.SchemaId.Name);
ruleData["IndexName"] = formatter.FormatString(action.IndexName, @event);
var timestamp = @event.Headers.Timestamp().ToString();
@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
new JProperty("createdBy", created.Actor.ToString()),
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", created.Actor.ToString()),
new JProperty("data", JObject.FromObject(created.Data, serializer)));
new JProperty("data", formatter.ToRouteData(created.Data)));
break;
}
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
ruleData["Content"] = new JObject(
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", updated.Actor.ToString()),
new JProperty("data", JObject.FromObject(updated.Data, serializer)));
new JProperty("data", formatter.ToRouteData(updated.Data)));
break;
}

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

@ -24,14 +24,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction>
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private readonly RuleEventFormatter formatter;
private readonly JsonSerializer serializer;
public WebhookActionHandler(JsonSerializer serializer)
public WebhookActionHandler(RuleEventFormatter formatter)
{
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(formatter, nameof(formatter));
this.serializer = serializer;
this.formatter = formatter;
}
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, WebhookAction action)
@ -55,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
return new JObject(
new JProperty("type", eventName),
new JProperty("payload", JObject.FromObject(@event.Payload, serializer)),
new JProperty("payload", formatter.ToRouteData(@event.Payload)),
new JProperty("timestamp", @event.Headers.Timestamp().ToString()));
}

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

@ -0,0 +1,137 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// =========================================-=================================
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules
{
public class RuleEventFormatter
{
private const string Undefined = "UNDEFINED";
private const string AppIdPlaceholder = "$APP_ID";
private const string AppNamePlaceholder = "$APP_NAME";
private const string SchemaIdPlaceholder = "$SCHEMA_ID";
private const string SchemaNamePlaceholder = "$SCHEMA_NAME";
private const string TimestampDatePlaceholder = "$TIMESTAMP_DATE";
private const string TimestampDateTimePlaceholder = "$TIMESTAMP_DATETIME";
private static readonly Regex ContentDataPlaceholder = new Regex(@"\$CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled);
private readonly JsonSerializer serializer;
public RuleEventFormatter(JsonSerializer serializer)
{
Guard.NotNull(serializer, nameof(serializer));
this.serializer = serializer;
}
public virtual JToken ToRouteData(object value)
{
return JToken.FromObject(value, serializer);
}
public virtual string FormatString(string text, Envelope<AppEvent> @event)
{
var sb = new StringBuilder(text);
if (@event.Headers.Contains(CommonHeaders.Timestamp))
{
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)
{
sb.Replace(AppIdPlaceholder, @event.Payload.AppId.Id.ToString());
sb.Replace(AppNamePlaceholder, @event.Payload.AppId.Name);
}
if (@event.Payload is SchemaEvent schemaEvent && schemaEvent.SchemaId != null)
{
sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString());
sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name);
}
var result = sb.ToString();
if (@event.Payload is ContentCreated contentCreated && contentCreated.Data != null)
{
result = ReplaceData(contentCreated.Data, result);
}
if (@event.Payload is ContentUpdated contentUpdated && contentUpdated.Data != null)
{
result = ReplaceData(contentUpdated.Data, result);
}
return result;
}
private static string ReplaceData(NamedContentData data, string text)
{
return ContentDataPlaceholder.Replace(text, match =>
{
var captures = match.Groups[2].Captures;
var path = new string[captures.Count];
for (var i = 0; i < path.Length; i++)
{
path[i] = captures[i].Value;
}
if (!data.TryGetValue(path[0], out var field))
{
return Undefined;
}
if (!field.TryGetValue(path[1], out var value))
{
return Undefined;
}
for (var j = 2; j < path.Length; j++)
{
if (value is JObject obj && obj.TryGetValue(path[j], out value))
{
continue;
}
if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count)
{
value = arr[idx];
}
else
{
return Undefined;
}
}
if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined)
{
return Undefined;
}
if (value is JValue jValue && jValue != null)
{
return jValue.Value.ToString();
}
return value?.ToString(Formatting.Indented) ?? Undefined;
});
}
}
}

2
src/Squidex.Domain.Apps.Events/SquidexEvent.cs

@ -12,8 +12,6 @@ namespace Squidex.Domain.Apps.Events
{
public abstract class SquidexEvent : IEvent
{
public string Username { get; set; }
public RefToken Actor { get; set; }
}
}

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

@ -0,0 +1,246 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
public class RuleEventFormatterTests
{
private readonly JsonSerializer serializer = JsonSerializer.CreateDefault();
private readonly RuleEventFormatter sut;
public RuleEventFormatterTests()
{
sut = new RuleEventFormatter(serializer);
}
[Fact]
public void Should_serialize_object_to_json()
{
var result = sut.ToRouteData(new { Value = 1 });
Assert.True(result is JObject);
}
[Fact]
public void Should_replace_app_information_from_event()
{
var appId = Guid.NewGuid();
var @event = new ContentCreated
{
AppId = new NamedId<Guid>(appId, "my-app")
};
var result = sut.FormatString("Name $APP_NAME has id $APP_ID", Envelope.Create(@event).To<AppEvent>());
Assert.Equal($"Name my-app has id {appId}", result);
}
[Fact]
public void Should_replace_schema_information_from_event()
{
var schemaId = Guid.NewGuid();
var @event = new ContentCreated
{
SchemaId = new NamedId<Guid>(schemaId, "my-schema")
};
var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", Envelope.Create(@event).To<AppEvent>());
Assert.Equal($"Name my-schema has id {schemaId}", result);
}
[Fact]
public void Should_replace_timestamp_information_from_event()
{
var now = DateTime.UtcNow;
var envelope = Envelope.Create(new ContentCreated()).To<AppEvent>().SetTimestamp(Instant.FromDateTimeUtc(now));
var result = sut.FormatString("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope);
Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result);
}
[Fact]
public void Should_return_undefined_when_field_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.country.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_partition_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.city.de", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_array_item_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JArray()))
};
var result = sut.FormatString("$CONTENT_DATA.city.de.10", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_property_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JObject(
new JProperty("name", "Berlin"))))
};
var result = sut.FormatString("$CONTENT_DATA.city.de.Name", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_plain_value_when_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_undefined_when_null()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", JValue.CreateNull()))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_undefined()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", JValue.CreateUndefined()))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_string_when_object()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JObject(
new JProperty("name", "Berlin"))))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result);
}
[Fact]
public void Should_return_plain_value_from_array_when_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JArray(
"Berlin")))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv.0", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_plain_value_from_object_when_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JObject(
new JProperty("name", "Berlin"))))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv.name", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("Berlin", result);
}
}
}
Loading…
Cancel
Save