Browse Source

Started refactorings. just have to commit something.

pull/315/head
Sebastian Stehle 8 years ago
parent
commit
687c22bc30
  1. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionAttribute.cs
  2. 40
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  3. 30
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs
  4. 3
      src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaAction.cs
  5. 39
      src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaActionHandler.cs
  6. 3
      src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueAction.cs
  7. 49
      src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueActionHandler.cs
  8. 42
      src/Squidex.Domain.Apps.Rules/Actions/Discourse/DiscourseAction.cs
  9. 48
      src/Squidex.Domain.Apps.Rules/Actions/Discourse/DiscourseActionHandler.cs
  10. 3
      src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchAction.cs
  11. 49
      src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  12. 3
      src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyAction.cs
  13. 52
      src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyActionHandler.cs
  14. 3
      src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumAction.cs
  15. 72
      src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumActionHandler.cs
  16. 106
      src/Squidex.Domain.Apps.Rules/Actions/RuleActionRegistry.cs
  17. 32
      src/Squidex.Domain.Apps.Rules/Actions/RuleElement.cs
  18. 92
      src/Squidex.Domain.Apps.Rules/Actions/RuleElementRegistry.cs
  19. 3
      src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackAction.cs
  20. 68
      src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackActionHandler.cs
  21. 3
      src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetAction.cs
  22. 50
      src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetActionHandler.cs
  23. 5
      src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookAction.cs
  24. 84
      src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookActionHandler.cs
  25. 1
      src/Squidex.Domain.Apps.Rules/Squidex.Domain.Apps.Rules.csproj
  26. 4
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs
  27. 3
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionSerializer.cs
  28. 31
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs
  29. 50
      src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  30. 2
      src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs
  31. 2
      src/Squidex/Config/Domain/RuleServices.cs
  32. 2
      src/Squidex/Config/Domain/SerializationServices.cs
  33. 2
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts
  34. 2
      src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts
  35. 2
      src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts
  36. 2
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts
  37. 2
      src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts
  38. 2
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts
  39. 2
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts
  40. 2
      src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts
  41. 40
      src/Squidex/app/features/rules/pages/rules/rule-action.container.ts
  42. 12
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  43. 112
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  44. 24
      src/Squidex/app/features/rules/pages/rules/rules-page.component.ts
  45. 81
      src/Squidex/app/shared/services/rules.service.ts

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

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.HandleRules
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class RuleActionAttribute : Attribute
{
public string Link { get; set; }
public string Display { get; set; }
public string Description { get; set; }
}
}

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

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
#pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar #pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar
@ -17,11 +18,50 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
public abstract class RuleActionHandler<TAction, TData> : IRuleActionHandler where TAction : RuleAction public abstract class RuleActionHandler<TAction, TData> : IRuleActionHandler where TAction : RuleAction
{ {
private readonly RuleEventFormatter formatter;
Type IRuleActionHandler.ActionType Type IRuleActionHandler.ActionType
{ {
get { return typeof(TAction); } get { return typeof(TAction); }
} }
protected RuleActionHandler(RuleEventFormatter formatter)
{
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
}
protected virtual string ToPayloadJson<T>(T @event)
{
return formatter.ToPayload<T>(@event).ToString();
}
protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{
return formatter.ToEnvelope(@event).ToString();
}
protected virtual JObject ToPayload<T>(T @event)
{
return formatter.ToPayload<T>(@event);
}
protected virtual JObject ToEnvelope(EnrichedEvent @event)
{
return formatter.ToEnvelope(@event);
}
protected string Format(Uri uri, EnrichedEvent @event)
{
return formatter.Format(uri.ToString(), @event);
}
protected string Format(string text, EnrichedEvent @event)
{
return formatter.Format(text, @event);
}
async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action) async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action)
{ {
var (description, data) = await CreateJobAsync(@event, (TAction)action); var (description, data) = await CreateJobAsync(@event, (TAction)action);

30
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs

@ -0,0 +1,30 @@
// ==========================================================================
// 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
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class RuleActionHandlerAttribute : Attribute
{
public Type HandlerType { get; }
public RuleActionHandlerAttribute(Type handlerType)
{
Guard.NotNull(handlerType, nameof(handlerType));
HandlerType = handlerType;
if (!typeof(IRuleActionHandler).IsAssignableFrom(handlerType))
{
throw new ArgumentException($"Handler type must implement {typeof(IRuleActionHandler)}.", nameof(handlerType));
}
}
}
}

3
src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaAction.cs

@ -6,10 +6,13 @@
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Rules.Action.Algolia namespace Squidex.Domain.Apps.Rules.Action.Algolia
{ {
[RuleActionHandler(typeof(AlgoliaActionHandler))]
[RuleAction(Description = "")]
public sealed class AlgoliaAction : RuleAction public sealed class AlgoliaAction : RuleAction
{ {
[Required] [Required]

39
src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaActionHandler.cs

@ -12,38 +12,18 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.Algolia namespace Squidex.Domain.Apps.Rules.Action.Algolia
{ {
public sealed class AlgoliaJob
{
public string AppId { get; set; }
public string ApiKey { get; set; }
public string ContentId { get; set; }
public string IndexName { get; set; }
public JObject Content { get; set; }
}
public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, AlgoliaJob> public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, AlgoliaJob>
{ {
private const string DescriptionIgnore = "Ignore"; private const string DescriptionIgnore = "Ignore";
private readonly ClientPool<(string AppId, string ApiKey, string IndexName), Index> clients; private readonly ClientPool<(string AppId, string ApiKey, string IndexName), Index> clients;
private readonly RuleEventFormatter formatter;
public AlgoliaActionHandler(RuleEventFormatter formatter) public AlgoliaActionHandler(RuleEventFormatter formatter)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
clients = new ClientPool<(string AppId, string ApiKey, string IndexName), Index>(key => clients = new ClientPool<(string AppId, string ApiKey, string IndexName), Index>(key =>
{ {
var client = new AlgoliaClient(key.AppId, key.ApiKey); var client = new AlgoliaClient(key.AppId, key.ApiKey);
@ -64,7 +44,7 @@ namespace Squidex.Domain.Apps.Rules.Action.Algolia
AppId = action.AppId, AppId = action.AppId,
ApiKey = action.ApiKey, ApiKey = action.ApiKey,
ContentId = contentId, ContentId = contentId,
IndexName = formatter.Format(action.IndexName, @event) IndexName = Format(action.IndexName, @event)
}; };
if (contentEvent.Type == EnrichedContentEventType.Deleted || if (contentEvent.Type == EnrichedContentEventType.Deleted ||
@ -76,7 +56,7 @@ namespace Squidex.Domain.Apps.Rules.Action.Algolia
{ {
ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; ruleDescription = $"Add entry to Algolia index: {action.IndexName}";
ruleJob.Content = formatter.ToPayload(contentEvent); ruleJob.Content = ToPayload(contentEvent);
ruleJob.Content["objectID"] = contentId; ruleJob.Content["objectID"] = contentId;
} }
@ -116,4 +96,17 @@ namespace Squidex.Domain.Apps.Rules.Action.Algolia
} }
} }
} }
public sealed class AlgoliaJob
{
public string AppId { get; set; }
public string ApiKey { get; set; }
public string ContentId { get; set; }
public string IndexName { get; set; }
public JObject Content { get; set; }
}
} }

3
src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueAction.cs

@ -8,11 +8,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Action.AzureQueue namespace Squidex.Domain.Apps.Rules.Action.AzureQueue
{ {
[RuleActionHandler(typeof(AzureQueueActionHandler))]
[RuleAction(Description = "")]
public sealed class AzureQueueAction : RuleAction public sealed class AzureQueueAction : RuleAction
{ {
[Required] [Required]

49
src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueActionHandler.cs

@ -9,46 +9,18 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue; using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.AzureQueue namespace Squidex.Domain.Apps.Rules.Action.AzureQueue
{ {
public sealed class AzureQueueJob
{
public string QueueConnectionString { get; set; }
public string QueueName { get; set; }
public string MessageBodyV2 { get; set; }
public JObject MessageBody { get; set; }
public string Body
{
get
{
return MessageBodyV2 ?? MessageBody.ToString(Formatting.Indented);
}
}
}
public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction, AzureQueueJob> public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction, AzureQueueJob>
{ {
private readonly ClientPool<(string ConnectionString, string QueueName), CloudQueue> clients; private readonly ClientPool<(string ConnectionString, string QueueName), CloudQueue> clients;
private readonly RuleEventFormatter formatter;
public AzureQueueActionHandler(RuleEventFormatter formatter) public AzureQueueActionHandler(RuleEventFormatter formatter)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
clients = new ClientPool<(string ConnectionString, string QueueName), CloudQueue>(key => clients = new ClientPool<(string ConnectionString, string QueueName), CloudQueue>(key =>
{ {
var storageAccount = CloudStorageAccount.Parse(key.ConnectionString); var storageAccount = CloudStorageAccount.Parse(key.ConnectionString);
@ -62,16 +34,14 @@ namespace Squidex.Domain.Apps.Rules.Action.AzureQueue
protected override (string Description, AzureQueueJob Data) CreateJob(EnrichedEvent @event, AzureQueueAction action) protected override (string Description, AzureQueueJob Data) CreateJob(EnrichedEvent @event, AzureQueueAction action)
{ {
var body = formatter.ToEnvelope(@event).ToString(Formatting.Indented); var queueName = Format(action.Queue, @event);
var queueName = formatter.Format(action.Queue, @event);
var ruleDescription = $"Send AzureQueueJob to azure queue '{action.Queue}'"; var ruleDescription = $"Send AzureQueueJob to azure queue '{queueName}'";
var ruleJob = new AzureQueueJob var ruleJob = new AzureQueueJob
{ {
QueueConnectionString = action.ConnectionString, QueueConnectionString = action.ConnectionString,
QueueName = queueName, QueueName = queueName,
MessageBodyV2 = body MessageBodyV2 = ToEnvelopeJson(@event)
}; };
return (ruleDescription, ruleJob); return (ruleDescription, ruleJob);
@ -81,9 +51,18 @@ namespace Squidex.Domain.Apps.Rules.Action.AzureQueue
{ {
var queue = clients.GetClient((job.QueueConnectionString, job.QueueName)); var queue = clients.GetClient((job.QueueConnectionString, job.QueueName));
await queue.AddMessageAsync(new CloudQueueMessage(job.Body)); await queue.AddMessageAsync(new CloudQueueMessage(job.MessageBodyV2));
return ("Completed", null); return ("Completed", null);
} }
} }
public sealed class AzureQueueJob
{
public string QueueConnectionString { get; set; }
public string QueueName { get; set; }
public string MessageBodyV2 { get; set; }
}
} }

42
src/Squidex.Domain.Apps.Rules/Actions/Discourse/DiscourseAction.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Actions.Discourse
{
[RuleActionHandler(typeof(DiscourseActionHandler))]
[RuleAction(Description = "")]
public sealed class DiscourseAction : RuleAction
{
[AbsoluteUrl]
[Required]
[Display(Name = "Url", Description = "he url to the discourse server.")]
public Uri Url { get; set; }
[Required]
[Display(Name = "Api Key", Description = "The api key.")]
public string ApiKey { get; set; }
[Required]
[Display(Name = "Text", Description = "The text as markdown.")]
public string Text { get; set; }
[Display(Name = "Title", Description = "The optional title when creating new topics.")]
public string Title { get; set; }
[Display(Name = "Topic", Description = "The optional topic id.")]
public int? Topic { get; set; }
[Display(Name = "Category", Description = "The optional category id.")]
public int? Category { get; set; }
}
}

48
src/Squidex.Domain.Apps.Rules/Actions/Discourse/DiscourseActionHandler.cs

@ -0,0 +1,48 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
namespace Squidex.Domain.Apps.Rules.Actions.Discourse
{
public sealed class DiscourseActionHandler : RuleActionHandler<DiscourseAction, DiscourseJob>
{
private readonly IHttpClientFactory httpClientFactory;
public DiscourseActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{
this.httpClientFactory = httpClientFactory;
}
protected override Task<(string Description, DiscourseJob Data)> CreateJobAsync(EnrichedEvent @event, DiscourseAction action)
{
return base.CreateJobAsync(@event, action);
}
protected override Task<(string Dump, Exception Exception)> ExecuteJobAsync(DiscourseJob job)
{
using (var client = httpClientFactory.CreateClient())
{
// Foo
}
return Task.FromResult<(string Dump, Exception Exception)>((string.Empty, null));
}
}
public sealed class DiscourseJob
{
public string RequestUrl { get; set; }
public string RequestBody { get; set; }
}
}

3
src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchAction.cs

@ -7,11 +7,14 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
{ {
[RuleActionHandler(typeof(ElasticSearchActionHandler))]
[RuleAction(Description = "")]
public sealed class ElasticSearchAction : RuleAction public sealed class ElasticSearchAction : RuleAction
{ {
[AbsoluteUrl] [AbsoluteUrl]

49
src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -11,42 +11,18 @@ using Elasticsearch.Net;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
{ {
public sealed class ElasticSearchJob
{
public string Host { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string ContentId { get; set; }
public string IndexName { get; set; }
public string IndexType { get; set; }
public JObject Content { get; set; }
}
public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearchAction, ElasticSearchJob> public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearchAction, ElasticSearchJob>
{ {
private const string DescriptionIgnore = "Ignore"; private const string DescriptionIgnore = "Ignore";
private readonly ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient> clients; private readonly ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient> clients;
private readonly RuleEventFormatter formatter;
public ElasticSearchActionHandler(RuleEventFormatter formatter) public ElasticSearchActionHandler(RuleEventFormatter formatter)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
clients = new ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient>(key => clients = new ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient>(key =>
{ {
var config = new ConnectionConfiguration(key.Host); var config = new ConnectionConfiguration(key.Host);
@ -73,8 +49,8 @@ namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
Username = action.Username, Username = action.Username,
Password = action.Password, Password = action.Password,
ContentId = contentId, ContentId = contentId,
IndexName = formatter.Format(action.IndexName, @event), IndexName = Format(action.IndexName, @event),
IndexType = formatter.Format(action.IndexType, @event) IndexType = Format(action.IndexType, @event)
}; };
if (contentEvent.Type == EnrichedContentEventType.Deleted || if (contentEvent.Type == EnrichedContentEventType.Deleted ||
@ -86,7 +62,7 @@ namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
{ {
ruleDescription = $"Upsert to index: {action.IndexName}"; ruleDescription = $"Upsert to index: {action.IndexName}";
ruleJob.Content = formatter.ToPayload(contentEvent); ruleJob.Content = ToPayload(contentEvent);
ruleJob.Content["objectID"] = contentId; ruleJob.Content["objectID"] = contentId;
} }
} }
@ -121,4 +97,21 @@ namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
} }
} }
} }
public sealed class ElasticSearchJob
{
public string Host { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string ContentId { get; set; }
public string IndexName { get; set; }
public string IndexType { get; set; }
public JObject Content { get; set; }
}
} }

3
src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyAction.cs

@ -6,10 +6,13 @@
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Rules.Action.Fastly namespace Squidex.Domain.Apps.Rules.Action.Fastly
{ {
[RuleActionHandler(typeof(FastlyActionHandler))]
[RuleAction(Description = "")]
public sealed class FastlyAction : RuleAction public sealed class FastlyAction : RuleAction
{ {
[Required] [Required]

52
src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyActionHandler.cs

@ -11,35 +11,22 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils; using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.Fastly namespace Squidex.Domain.Apps.Rules.Action.Fastly
{ {
public sealed class FastlyJob
{
public string FastlyApiKey { get; set; }
public string FastlyServiceID { get; set; }
public string Key { get; set; }
}
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 readonly ClientPool<string, HttpClient> clients; private readonly IHttpClientFactory httpClientFactory;
public FastlyActionHandler() public FastlyActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{ {
clients = new ClientPool<string, HttpClient>(key => Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
{
return new HttpClient this.httpClientFactory = httpClientFactory;
{
Timeout = TimeSpan.FromSeconds(2)
};
});
} }
protected override (string Description, FastlyJob Data) CreateJob(EnrichedEvent @event, FastlyAction action) protected override (string Description, FastlyJob Data) CreateJob(EnrichedEvent @event, FastlyAction action)
@ -56,19 +43,26 @@ namespace Squidex.Domain.Apps.Rules.Action.Fastly
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job)
{ {
var httpClient = clients.GetClient(string.Empty); using (var httpClient = httpClientFactory.CreateClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(2);
var requestUrl = $"https://api.fastly.com/service/{job.FastlyServiceID}/purge/{job.Key}";
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Headers.Add("Fastly-Key", job.FastlyApiKey);
return await httpClient.OneWayRequestAsync(BuildRequest(job), null); return await httpClient.OneWayRequestAsync(request, null);
}
} }
}
private static HttpRequestMessage BuildRequest(FastlyJob job) public sealed class FastlyJob
{ {
var requestUrl = $"https://api.fastly.com/service/{job.FastlyServiceID}/purge/{job.Key}"; public string FastlyApiKey { get; set; }
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Headers.Add("Fastly-Key", job.FastlyApiKey); public string FastlyServiceID { get; set; }
return request; public string Key { get; set; }
}
} }
} }

3
src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumAction.cs

@ -6,10 +6,13 @@
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Rules.Action.Medium namespace Squidex.Domain.Apps.Rules.Action.Medium
{ {
[RuleActionHandler(typeof(MediumActionHandler))]
[RuleAction(Description = "")]
public sealed class MediumAction : RuleAction public sealed class MediumAction : RuleAction
{ {
[Required] [Required]

72
src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumActionHandler.cs

@ -32,38 +32,22 @@ namespace Squidex.Domain.Apps.Rules.Action.Medium
{ {
private const string Description = "Post to medium"; private const string Description = "Post to medium";
private readonly RuleEventFormatter formatter; private readonly IHttpClientFactory httpClientFactory;
private readonly ClientPool<string, HttpClient> clients;
public MediumActionHandler(RuleEventFormatter formatter) public MediumActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter)); this.httpClientFactory = httpClientFactory;
this.formatter = formatter;
clients = new ClientPool<string, HttpClient>(key =>
{
var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(4)
};
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Accept-Charset", "utf-8");
client.DefaultRequestHeaders.Add("User-Agent", "Squidex Headless CMS");
return client;
});
} }
protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action) protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action)
{ {
var requestBody = var requestBody =
new JObject( new JObject(
new JProperty("title", formatter.Format(action.Title, @event)), new JProperty("title", Format(action.Title, @event)),
new JProperty("contentFormat", action.IsHtml ? "html" : "markdown"), new JProperty("contentFormat", action.IsHtml ? "html" : "markdown"),
new JProperty("content", formatter.Format(action.Content, @event)), new JProperty("content", Format(action.Content, @event)),
new JProperty("canonicalUrl", formatter.Format(action.CanonicalUrl, @event)), new JProperty("canonicalUrl", Format(action.CanonicalUrl, @event)),
new JProperty("tags", ParseTags(@event, action))); new JProperty("tags", ParseTags(@event, action)));
var ruleJob = new MediumJob { AccessToken = action.AccessToken, RequestBody = requestBody.ToString(Formatting.Indented) }; var ruleJob = new MediumJob { AccessToken = action.AccessToken, RequestBody = requestBody.ToString(Formatting.Indented) };
@ -81,7 +65,7 @@ namespace Squidex.Domain.Apps.Rules.Action.Medium
string[] tags; string[] tags;
try try
{ {
var jsonTags = formatter.Format(action.Tags, @event); var jsonTags = Format(action.Tags, @event);
tags = JsonConvert.DeserializeObject<string[]>(jsonTags); tags = JsonConvert.DeserializeObject<string[]>(jsonTags);
} }
@ -95,30 +79,36 @@ namespace Squidex.Domain.Apps.Rules.Action.Medium
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job)
{ {
var httpClient = clients.GetClient(string.Empty); using (var httpClient = httpClientFactory.CreateClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(4);
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
httpClient.DefaultRequestHeaders.Add("Accept-Charset", "utf-8");
httpClient.DefaultRequestHeaders.Add("User-Agent", "Squidex Headless CMS");
string id; string id;
HttpResponseMessage response = null; HttpResponseMessage response = null;
var meRequest = BuildMeRequest(job); var meRequest = BuildMeRequest(job);
try try
{ {
response = await httpClient.SendAsync(meRequest); response = await httpClient.SendAsync(meRequest);
var responseString = await response.Content.ReadAsStringAsync(); var responseString = await response.Content.ReadAsStringAsync();
var responseJson = JToken.Parse(responseString); var responseJson = JToken.Parse(responseString);
id = responseJson["data"]["id"].ToString(); id = responseJson["data"]["id"].ToString();
} }
catch (Exception ex) catch (Exception ex)
{ {
var requestDump = DumpFormatter.BuildDump(meRequest, response, ex.ToString()); var requestDump = DumpFormatter.BuildDump(meRequest, response, ex.ToString());
return (requestDump, ex); return (requestDump, ex);
} }
return await httpClient.OneWayRequestAsync(BuildPostRequest(job, id), job.RequestBody); return await httpClient.OneWayRequestAsync(BuildPostRequest(job, id), job.RequestBody);
}
} }
private static HttpRequestMessage BuildPostRequest(MediumJob job, string id) private static HttpRequestMessage BuildPostRequest(MediumJob job, string id)

106
src/Squidex.Domain.Apps.Rules/Actions/RuleActionRegistry.cs

@ -1,106 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Rules.Action.Algolia;
using Squidex.Domain.Apps.Rules.Action.AzureQueue;
using Squidex.Domain.Apps.Rules.Action.ElasticSearch;
using Squidex.Domain.Apps.Rules.Action.Fastly;
using Squidex.Domain.Apps.Rules.Action.Medium;
using Squidex.Domain.Apps.Rules.Action.Slack;
using Squidex.Domain.Apps.Rules.Action.Twitter;
using Squidex.Domain.Apps.Rules.Action.Webhook;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Actions
{
public static class RuleActionRegistry
{
private const string Suffix = "Action";
private static readonly HashSet<Type> ActionHandlerTypes = new HashSet<Type>();
private static readonly Dictionary<string, Type> ActionTypes = new Dictionary<string, Type>();
public static IReadOnlyDictionary<string, Type> Actions
{
get { return ActionTypes; }
}
public static IReadOnlyCollection<Type> ActionHandlers
{
get { return ActionHandlerTypes; }
}
static RuleActionRegistry()
{
Register<
AlgoliaAction,
AlgoliaActionHandler>();
Register<
AzureQueueAction,
AzureQueueActionHandler>();
Register<
ElasticSearchAction,
ElasticSearchActionHandler>();
Register<
FastlyAction,
FastlyActionHandler>();
Register<
MediumAction,
MediumActionHandler>();
Register<
SlackAction,
SlackActionHandler>();
Register<
TweetAction,
TweetActionHandler>();
Register<
WebhookAction,
WebhookActionHandler>();
}
public static void Register<TAction, THandler>() where TAction : RuleAction where THandler : IRuleActionHandler
{
AddActionType<TAction>();
AddActionHandler<THandler>();
}
private static void AddActionHandler<THandler>() where THandler : IRuleActionHandler
{
ActionHandlerTypes.Add(typeof(THandler));
}
private static void AddActionType<TAction>() where TAction : RuleAction
{
var name = typeof(TAction).Name;
if (name.EndsWith(Suffix, StringComparison.Ordinal))
{
name = name.Substring(0, name.Length - Suffix.Length);
}
ActionTypes.Add(name, typeof(TAction));
}
public static void RegisterTypes(TypeNameRegistry typeNameRegistry)
{
foreach (var actionType in ActionTypes.Values)
{
typeNameRegistry.Map(actionType, actionType.Name);
}
}
}
}

32
src/Squidex.Domain.Apps.Rules/Actions/RuleElement.cs

@ -0,0 +1,32 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Rules.Actions
{
public sealed class RuleElement
{
public Type Type { get; }
public string Link { get; set; }
public string Display { get; }
public string Description { get; }
public RuleElement(Type type, string display, string description, string link = null)
{
Type = type;
Display = display;
Description = description;
Link = link;
}
}
}

92
src/Squidex.Domain.Apps.Rules/Actions/RuleElementRegistry.cs

@ -0,0 +1,92 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Actions
{
public static class RuleElementRegistry
{
private const string Suffix = "Action";
private static readonly HashSet<Type> ActionHandlerTypes = new HashSet<Type>();
private static readonly Dictionary<string, RuleElement> ActionTypes = new Dictionary<string, RuleElement>();
private static readonly Dictionary<string, RuleElement> TriggerTypes = new Dictionary<string, RuleElement>();
public static IReadOnlyDictionary<string, RuleElement> Actions
{
get { return ActionTypes; }
}
public static IReadOnlyDictionary<string, RuleElement> Triggers
{
get { return TriggerTypes; }
}
public static IReadOnlyCollection<Type> ActionHandlers
{
get { return ActionHandlerTypes; }
}
static RuleElementRegistry()
{
TriggerTypes["ContentChanged"] =
new RuleElement(
typeof(ContentChangedTrigger),
"Content changed",
"Content changed like created, updated, published, unpublished...");
TriggerTypes["AssetChanged"] =
new RuleElement(
typeof(AssetChangedTrigger),
"Asset changed",
"Asset changed like created, updated, renamed...");
var actionTypes =
typeof(RuleElementRegistry).Assembly
.GetTypes()
.Where(x => typeof(RuleAction).IsAssignableFrom(x))
.Where(x => x.GetCustomAttribute<RuleActionAttribute>() != null)
.Where(x => x.GetCustomAttribute<RuleActionHandlerAttribute>() != null)
.ToList();
foreach (var actionType in actionTypes)
{
var name = actionType.Name;
if (name.EndsWith(Suffix, StringComparison.Ordinal))
{
name = name.Substring(0, name.Length - Suffix.Length);
}
var metadata = actionType.GetCustomAttribute<RuleActionAttribute>();
ActionTypes[name] =
new RuleElement(actionType,
metadata.Display,
metadata.Description,
metadata.Link);
ActionHandlerTypes.Add(actionType.GetCustomAttribute<RuleActionHandlerAttribute>().HandlerType);
}
}
public static void RegisterTypes(TypeNameRegistry typeNameRegistry)
{
foreach (var actionType in ActionTypes.Values)
{
typeNameRegistry.Map(actionType.Type, actionType.Type.Name);
}
}
}
}

3
src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackAction.cs

@ -7,11 +7,14 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Action.Slack namespace Squidex.Domain.Apps.Rules.Action.Slack
{ {
[RuleActionHandler(typeof(SlackActionHandler))]
[RuleAction(Description = "")]
public sealed class SlackAction : RuleAction public sealed class SlackAction : RuleAction
{ {
[AbsoluteUrl] [AbsoluteUrl]

68
src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackActionHandler.cs

@ -16,79 +16,57 @@ using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.Slack namespace Squidex.Domain.Apps.Rules.Action.Slack
{ {
public sealed class SlackJob
{
public string RequestUrl { get; set; }
public string RequestBodyV2 { get; set; }
public JObject RequestBody { get; set; }
public string Body
{
get
{
return RequestBodyV2 ?? RequestBody.ToString(Formatting.Indented);
}
}
}
public sealed class SlackActionHandler : RuleActionHandler<SlackAction, SlackJob> public sealed class SlackActionHandler : RuleActionHandler<SlackAction, SlackJob>
{ {
private const string Description = "Send message to slack"; private const string Description = "Send message to slack";
private readonly RuleEventFormatter formatter; private readonly IHttpClientFactory httpClientFactory;
private readonly ClientPool<string, HttpClient> clients;
public SlackActionHandler(RuleEventFormatter formatter) public SlackActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter)); Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
this.formatter = formatter;
clients = new ClientPool<string, HttpClient>(key => this.httpClientFactory = httpClientFactory;
{
return new HttpClient
{
Timeout = TimeSpan.FromSeconds(2)
};
});
} }
protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action) protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action)
{ {
var body = var body =
new JObject( new JObject(
new JProperty("text", formatter.Format(action.Text, @event))); new JProperty("text", Format(action.Text, @event)));
var ruleJob = new SlackJob var ruleJob = new SlackJob
{ {
RequestUrl = action.WebhookUrl.ToString(), RequestUrl = action.WebhookUrl.ToString(),
RequestBodyV2 = body.ToString(Formatting.Indented) RequestBody = body.ToString(Formatting.Indented)
}; };
return (Description, ruleJob); return (Description, ruleJob);
} }
protected override Task<(string Dump, Exception Exception)> ExecuteJobAsync(SlackJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(SlackJob job)
{ {
var httpClient = clients.GetClient(string.Empty); using (var httpClient = httpClientFactory.CreateClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(2);
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl)
{
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json")
};
return httpClient.OneWayRequestAsync(BuildRequest(job, job.Body), job.Body); return await httpClient.OneWayRequestAsync(request, job.RequestBody);
}
} }
}
private static HttpRequestMessage BuildRequest(SlackJob job, string requestBody) public sealed class SlackJob
{ {
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) public string RequestUrl { get; set; }
{
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};
return request; public string RequestBody { get; set; }
}
} }
} }

3
src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetAction.cs

@ -6,10 +6,13 @@
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Rules.Action.Twitter namespace Squidex.Domain.Apps.Rules.Action.Twitter
{ {
[RuleActionHandler(typeof(TweetActionHandler))]
[RuleAction(Description = "")]
public sealed class TweetAction : RuleAction public sealed class TweetAction : RuleAction
{ {
[Required] [Required]

50
src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetActionHandler.cs

@ -13,43 +13,27 @@ using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.Twitter namespace Squidex.Domain.Apps.Rules.Action.Twitter
{ {
public sealed class TweetJob
{
public string AccessToken { get; set; }
public string AccessSecret { get; set; }
public string Text { get; set; }
}
public sealed class TweetActionHandler : RuleActionHandler<TweetAction, TweetJob> public sealed class TweetActionHandler : RuleActionHandler<TweetAction, TweetJob>
{ {
private const string Description = "Send a tweet"; private const string Description = "Send a tweet";
private readonly RuleEventFormatter formatter;
private readonly TwitterOptions twitterOptions; private readonly TwitterOptions twitterOptions;
public TweetActionHandler(RuleEventFormatter formatter, IOptions<TwitterOptions> twitterOptions) public TweetActionHandler(RuleEventFormatter formatter, IOptions<TwitterOptions> twitterOptions)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter));
Guard.NotNull(twitterOptions, nameof(twitterOptions)); Guard.NotNull(twitterOptions, nameof(twitterOptions));
this.formatter = formatter;
this.twitterOptions = twitterOptions.Value; this.twitterOptions = twitterOptions.Value;
} }
protected override (string Description, TweetJob Data) CreateJob(EnrichedEvent @event, TweetAction action) protected override (string Description, TweetJob Data) CreateJob(EnrichedEvent @event, TweetAction action)
{ {
var text = formatter.Format(action.Text, @event);
var ruleJob = new TweetJob var ruleJob = new TweetJob
{ {
Text = text, Text = Format(action.Text, @event),
AccessToken = action.AccessToken, AccessToken = action.AccessToken,
AccessSecret = action.AccessSecret AccessSecret = action.AccessSecret
}; };
@ -59,22 +43,24 @@ namespace Squidex.Domain.Apps.Rules.Action.Twitter
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(TweetJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(TweetJob job)
{ {
try var tokens = Tokens.Create(
{ twitterOptions.ClientId,
var tokens = Tokens.Create( twitterOptions.ClientSecret,
twitterOptions.ClientId, job.AccessToken,
twitterOptions.ClientSecret, job.AccessSecret);
job.AccessToken,
job.AccessSecret);
var response = await tokens.Statuses.UpdateAsync(status => job.Text); var response = await tokens.Statuses.UpdateAsync(status => job.Text);
return ($"Tweeted: {job.Text}", null); return ($"Tweeted: {job.Text}", null);
}
catch (Exception ex)
{
return (ex.Message, ex);
}
} }
} }
public sealed class TweetJob
{
public string AccessToken { get; set; }
public string AccessSecret { get; set; }
public string Text { get; set; }
}
} }

5
src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookAction.cs

@ -7,16 +7,19 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Action.Webhook namespace Squidex.Domain.Apps.Rules.Action.Webhook
{ {
[RuleActionHandler(typeof(WebhookActionHandler))]
[RuleAction(Description = "")]
public sealed class WebhookAction : RuleAction public sealed class WebhookAction : RuleAction
{ {
[AbsoluteUrl] [AbsoluteUrl]
[Required] [Required]
[Display(Name = "Url", Description = "he url of the webhook.")] [Display(Name = "Url", Description = "he url to the webhook.")]
public Uri Url { get; set; } public Uri Url { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the signature.")] [Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the signature.")]

84
src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookActionHandler.cs

@ -9,93 +9,65 @@ using System;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils; using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.Webhook namespace Squidex.Domain.Apps.Rules.Action.Webhook
{ {
public sealed class WebhookJob
{
public string RequestUrl { get; set; }
public string RequestSignature { get; set; }
public string RequestBodyV2 { get; set; }
public JObject RequestBody { get; set; }
public string Body
{
get
{
return RequestBodyV2 ?? RequestBody.ToString(Formatting.Indented);
}
}
}
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, WebhookJob> public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, WebhookJob>
{ {
private readonly RuleEventFormatter formatter; private readonly IHttpClientFactory httpClientFactory;
private readonly ClientPool<string, HttpClient> clients;
public WebhookActionHandler(RuleEventFormatter formatter) public WebhookActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{ {
Guard.NotNull(formatter, nameof(formatter)); Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
this.formatter = formatter;
clients = new ClientPool<string, HttpClient>(key => this.httpClientFactory = httpClientFactory;
{
var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(4)
};
client.DefaultRequestHeaders.Add("User-Agent", "Squidex Webhook");
return client;
});
} }
protected override (string Description, WebhookJob Data) CreateJob(EnrichedEvent @event, WebhookAction action) protected override (string Description, WebhookJob Data) CreateJob(EnrichedEvent @event, WebhookAction action)
{ {
var requestBody = formatter.ToEnvelope(@event).ToString(Formatting.Indented); var requestBody = ToEnvelopeJson(@event);
var requestUrl = formatter.Format(action.Url.ToString(), @event); var requestUrl = Format(action.Url, @event);
var ruleDescription = $"Send event to webhook '{requestUrl}'"; var ruleDescription = $"Send event to webhook '{requestUrl}'";
var ruleJob = new WebhookJob var ruleJob = new WebhookJob
{ {
RequestUrl = requestUrl, RequestUrl = Format(action.Url.ToString(), @event),
RequestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64(), RequestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64(),
RequestBodyV2 = requestBody RequestBody = requestBody
}; };
return (ruleDescription, ruleJob); return (ruleDescription, ruleJob);
} }
protected override Task<(string Dump, Exception Exception)> ExecuteJobAsync(WebhookJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(WebhookJob job)
{ {
var httpClient = clients.GetClient(string.Empty); using (var httpClient = httpClientFactory.CreateClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl)
{
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json")
};
request.Headers.Add("X-Signature", job.RequestSignature);
request.Headers.Add("X-Application", "Squidex Webhook");
request.Headers.Add("User-Agent", "Squidex Webhook");
return httpClient.OneWayRequestAsync(BuildRequest(job, job.Body), job.Body); return await httpClient.OneWayRequestAsync(request, job.RequestBody);
}
} }
}
private static HttpRequestMessage BuildRequest(WebhookJob job, string requestBody) public sealed class WebhookJob
{ {
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) public string RequestUrl { get; set; }
{
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};
request.Headers.Add("X-Signature", job.RequestSignature); public string RequestSignature { get; set; }
return request; public string RequestBody { get; set; }
}
} }
} }

1
src/Squidex.Domain.Apps.Rules/Squidex.Domain.Apps.Rules.csproj

@ -15,6 +15,7 @@
<PackageReference Include="Algolia.Search" Version="5.1.0" /> <PackageReference Include="Algolia.Search" Version="5.1.0" />
<PackageReference Include="CoreTweet" Version="0.9.0.415" /> <PackageReference Include="CoreTweet" Version="0.9.0.415" />
<PackageReference Include="Elasticsearch.Net" Version="6.2.0" /> <PackageReference Include="Elasticsearch.Net" Version="6.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NodaTime" Version="2.3.0" /> <PackageReference Include="NodaTime" Version="2.3.0" />

4
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs

@ -36,9 +36,9 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
IsRequired = true IsRequired = true
}; };
foreach (var derived in RuleActionRegistry.Actions) foreach (var derived in RuleElementRegistry.Actions)
{ {
var derivedSchema = await context.SchemaGenerator.GenerateAsync(derived.Value, context.SchemaResolver); var derivedSchema = await context.SchemaGenerator.GenerateAsync(derived.Value.Type, context.SchemaResolver);
var oldName = context.Document.Definitions.FirstOrDefault(x => x.Value == derivedSchema).Key; var oldName = context.Document.Definitions.FirstOrDefault(x => x.Value == derivedSchema).Key;

3
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionSerializer.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Rules.Actions; using Squidex.Domain.Apps.Rules.Actions;
@ -13,7 +14,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
public sealed class RuleActionSerializer : JsonInheritanceConverter public sealed class RuleActionSerializer : JsonInheritanceConverter
{ {
public RuleActionSerializer() public RuleActionSerializer()
: base("actionType", typeof(RuleAction), RuleActionRegistry.Actions) : base("actionType", typeof(RuleAction), RuleElementRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type))
{ {
} }
} }

31
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{
public sealed class RuleElementDto
{
/// <summary>
/// Describes the action or trigger type.
/// </summary>
[Required]
public string Description { get; set; }
/// <summary>
/// The label for the action or trigger type.
/// </summary>
[Required]
public string Display { get; set; }
/// <summary>
/// The optional link to the product that is integrated.
/// </summary>
public string Link { get; set; }
}
}

50
src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -15,7 +16,9 @@ using Squidex.Areas.Api.Controllers.Rules.Models;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Rules.Actions;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline; using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.Rules namespace Squidex.Areas.Api.Controllers.Rules
@ -42,6 +45,53 @@ namespace Squidex.Areas.Api.Controllers.Rules
this.ruleEventsRepository = ruleEventsRepository; this.ruleEventsRepository = ruleEventsRepository;
} }
/// <summary>
/// Get the supported rule actions.
/// </summary>
/// <returns>
/// 200 => Rule actions returned.
/// </returns>
[HttpGet]
[Route("rules/actions/")]
[ProducesResponseType(typeof(Dictionary<string, RuleElementDto>), 200)]
[ApiCosts(0)]
public IActionResult GetActions()
{
var response = RuleElementRegistry.Actions.ToDictionary(x => x.Key, x => SimpleMapper.Map(x.Value, new RuleElementDto()));
return Ok(response);
}
/// <summary>
/// Get the supported rule triggers.
/// </summary>
/// <returns>
/// 200 => Rule triggers returned.
/// </returns>
[HttpGet]
[Route("rules/actions/")]
[ProducesResponseType(typeof(Dictionary<string, RuleElementDto>), 200)]
[ApiCosts(0)]
public IActionResult GetTriggers()
{
var response = new Dictionary<string, RuleElementDto>
{
["ContentChanged"] = new RuleElementDto
{
Display = "Content changed",
Description = "Content changed like created, updated, published, unpublished..."
},
["AssetChanged"] = new RuleElementDto
{
Display = "Asset changed",
Description = "Asset changed like created, updated, renamed..."
}
};
return Ok(response);
}
/// <summary> /// <summary>
/// Get rules. /// Get rules.
/// </summary> /// </summary>

2
src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs

@ -60,7 +60,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
public static Type[] Subtypes() public static Type[] Subtypes()
{ {
var type = typeof(SchemaPropertiesDto); var type = typeof(FieldPropertiesDto);
return type.Assembly.GetTypes().Where(type.IsAssignableFrom).ToArray(); return type.Assembly.GetTypes().Where(type.IsAssignableFrom).ToArray();
} }

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

@ -36,7 +36,7 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<RuleService>() services.AddSingletonAs<RuleService>()
.AsSelf(); .AsSelf();
foreach (var actionHandler in RuleActionRegistry.ActionHandlers) foreach (var actionHandler in RuleElementRegistry.ActionHandlers)
{ {
services.AddSingleton(typeof(IRuleActionHandler), actionHandler); services.AddSingleton(typeof(IRuleActionHandler), actionHandler);
} }

2
src/Squidex/Config/Domain/SerializationServices.cs

@ -39,7 +39,7 @@ namespace Squidex.Config.Domain
private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
{ {
RuleActionRegistry.RegisterTypes(TypeNameRegistry); RuleElementRegistry.RegisterTypes(TypeNameRegistry);
settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry); settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry);

2
src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts

@ -14,6 +14,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
templateUrl: './algolia-action.component.html' templateUrl: './algolia-action.component.html'
}) })
export class AlgoliaActionComponent implements OnInit { export class AlgoliaActionComponent implements OnInit {
public static key = 'Algolia';
@Input() @Input()
public action: any; public action: any;

2
src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts

@ -16,6 +16,8 @@ import { ValidatorsEx } from '@app/shared';
templateUrl: './azure-queue-action.component.html' templateUrl: './azure-queue-action.component.html'
}) })
export class AzureQueueActionComponent implements OnInit { export class AzureQueueActionComponent implements OnInit {
public static key = 'AzureQueue';
@Input() @Input()
public action: any; public action: any;

2
src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts

@ -14,6 +14,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
templateUrl: './elastic-search-action.component.html' templateUrl: './elastic-search-action.component.html'
}) })
export class ElasticSearchActionComponent implements OnInit { export class ElasticSearchActionComponent implements OnInit {
public static key = 'ElasticSearch';
@Input() @Input()
public action: any; public action: any;

2
src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts

@ -14,6 +14,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
templateUrl: './fastly-action.component.html' templateUrl: './fastly-action.component.html'
}) })
export class FastlyActionComponent implements OnInit { export class FastlyActionComponent implements OnInit {
public static key = 'Fastly';
@Input() @Input()
public action: any; public action: any;

2
src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts

@ -14,6 +14,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
templateUrl: './medium-action.component.html' templateUrl: './medium-action.component.html'
}) })
export class MediumActionComponent implements OnInit { export class MediumActionComponent implements OnInit {
public static key = 'Medium';
@Input() @Input()
public action: any; public action: any;

2
src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts

@ -14,6 +14,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
templateUrl: './slack-action.component.html' templateUrl: './slack-action.component.html'
}) })
export class SlackActionComponent implements OnInit { export class SlackActionComponent implements OnInit {
public static key = 'Slack';
@Input() @Input()
public action: any; public action: any;

2
src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts

@ -17,6 +17,8 @@ import { DialogService } from '@app/shared';
templateUrl: './tweet-action.component.html' templateUrl: './tweet-action.component.html'
}) })
export class TweetActionComponent implements OnInit { export class TweetActionComponent implements OnInit {
public static key = 'Tweet';
private request: any; private request: any;
@Input() @Input()

2
src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts

@ -14,6 +14,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
templateUrl: './webhook-action.component.html' templateUrl: './webhook-action.component.html'
}) })
export class WebhookActionComponent implements OnInit { export class WebhookActionComponent implements OnInit {
public static key = 'Webhook';
@Input() @Input()
public action: any; public action: any;

40
src/Squidex/app/features/rules/pages/rules/rule-action.container.ts

@ -0,0 +1,40 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ComponentFactoryResolver, ComponentRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { FormGroup } from '@angular/forms';
export class RuleActionContainer implements OnInit {
@Input()
public actionType: string;
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
@ViewChild('container', { read: ViewContainerRef })
public entry: ViewContainerRef;
private component: ComponentRef<any>;
constructor(
private readonly componentFactoryResolver: ComponentFactoryResolver
) {
}
public ngOnInit() {
const factories = Array.from(this.componentFactoryResolver['_factories'].values());
const factory: any = factories.find((x: any) => x.selector === this.actionType);
this.component = this.entry.createComponent(factory);
}
}

12
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts

@ -12,10 +12,9 @@ import {
CreateRuleDto, CreateRuleDto,
Form, Form,
ImmutableArray, ImmutableArray,
ruleActions,
RuleDto, RuleDto,
RuleElementDto,
RulesState, RulesState,
ruleTriggers,
SchemaDto SchemaDto
} from '@app/shared'; } from '@app/shared';
@ -29,9 +28,6 @@ export const MODE_EDIT_ACTION = 'EditAction';
templateUrl: './rule-wizard.component.html' templateUrl: './rule-wizard.component.html'
}) })
export class RuleWizardComponent implements OnInit { export class RuleWizardComponent implements OnInit {
public ruleActions = ruleActions;
public ruleTriggers = ruleTriggers;
public actionForm = new Form<FormGroup>(new FormGroup({})); public actionForm = new Form<FormGroup>(new FormGroup({}));
public actionType: string; public actionType: string;
public action: any = {}; public action: any = {};
@ -45,6 +41,12 @@ export class RuleWizardComponent implements OnInit {
@Output() @Output()
public completed = new EventEmitter(); public completed = new EventEmitter();
@Input()
public ruleActions: { [name: string]: RuleElementDto };
@Input()
public ruleTriggers: { [name: string]: RuleElementDto };
@Input() @Input()
public schemas: ImmutableArray<SchemaDto>; public schemas: ImmutableArray<SchemaDto>;

112
src/Squidex/app/features/rules/pages/rules/rules-page.component.html

@ -20,58 +20,68 @@
<ng-container content> <ng-container content>
<ng-container *ngIf="rulesState.rules | async; let rules"> <ng-container *ngIf="rulesState.rules | async; let rules">
<div class="table-items-row table-items-row-empty" *ngIf="rules.length === 0"> <ng-container *ngIf="ruleActions && ruleTriggers">
No Rule created yet. <div class="table-items-row table-items-row-empty" *ngIf="rules.length === 0">
No Rule created yet.
<button class="btn btn-success btn-sm ml-2" (click)="createNew()"> <button class="btn btn-success btn-sm ml-2" (click)="createNew()">
<i class="icon icon-plus"></i> Add Rule <i class="icon icon-plus"></i> Add Rule
</button> </button>
</div> </div>
<table class="table table-items table-fixed" *ngIf="rules.length > 0"> <table class="table table-items table-fixed" *ngIf="rules.length > 0">
<tbody *ngFor="let rule of rules; trackBy: trackByRule"> <tbody *ngFor="let rule of rules; trackBy: trackByRule">
<tr> <tr>
<td class="cell-separator"> <td class="cell-separator">
<h3>If</h3> <h3>If</h3>
</td> </td>
<td class="cell-auto"> <td class="cell-auto">
<span class="rule-element rule-element-{{rule.triggerType}}" (click)="editTrigger(rule)"> <span class="rule-element rule-element-{{rule.triggerType}}" (click)="editTrigger(rule)">
<span class="rule-element-icon"> <span class="rule-element-icon">
<i class="icon-trigger-{{rule.triggerType}}"></i> <i class="icon-trigger-{{rule.triggerType}}"></i>
</span>
<span class="rule-element-text">
{{ruleTriggers[rule.triggerType].display}}
</span>
</span> </span>
<span class="rule-element-text"> </td>
{{ruleTriggers[rule.triggerType].name}} <td class="cell-separator">
<h3>then</h3>
</td>
<td class="cell-auto">
<span class="rule-element rule-element-{{rule.actionType}}" (click)="editAction(rule)">
<span class="rule-element-icon">
<i class="icon-action-{{rule.actionType}}"></i>
</span>
<span class="rule-element-text">
{{ruleActions[rule.actionType].display}}
</span>
</span> </span>
</span> </td>
</td> <td class="cell-actions">
<td class="cell-separator"> <sqx-toggle [ngModel]="rule.isEnabled" (ngModelChange)="toggle(rule)"></sqx-toggle>
<h3>then</h3> </td>
</td> <td class="cell-actions">
<td class="cell-auto"> <button type="button" class="btn btn-link btn-danger"
<span class="rule-element rule-element-{{rule.actionType}}" (click)="editAction(rule)"> (sqxConfirmClick)="delete(rule)"
<span class="rule-element-icon"> confirmTitle="Delete rule"
<i class="icon-action-{{rule.actionType}}"></i> confirmText="Do you really want to delete the rule?">
</span> <i class="icon-bin2"></i>
<span class="rule-element-text"> </button>
{{ruleActions[rule.actionType].name}} </td>
</span> </tr>
</span> <tr class="spacer"></tr>
</td> </tbody>
<td class="cell-actions"> </table>
<sqx-toggle [ngModel]="rule.isEnabled" (ngModelChange)="toggle(rule)"></sqx-toggle>
</td> <ng-container *sqxModalView="addRuleDialog;onRoot:true;closeAuto:false">
<td class="cell-actions"> <sqx-rule-wizard [schemas]="schemasState.schemas | async" [rule]="wizardRule" [mode]="wizardMode"
<button type="button" class="btn btn-link btn-danger" [ruleActions]="ruleActions"
(sqxConfirmClick)="delete(rule)" [ruleTriggers]="ruleTriggers"
confirmTitle="Delete rule" (completed)="addRuleDialog.hide()">
confirmText="Do you really want to delete the rule?"> </sqx-rule-wizard>
<i class="icon-bin2"></i> </ng-container>
</button> </ng-container>
</td>
</tr>
<tr class="spacer"></tr>
</tbody>
</table>
</ng-container> </ng-container>
</ng-container> </ng-container>
@ -90,10 +100,4 @@
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>
<ng-container *sqxModalView="addRuleDialog;onRoot:true;closeAuto:false">
<sqx-rule-wizard [schemas]="schemasState.schemas | async" [rule]="wizardRule" [mode]="wizardMode"
(completed)="addRuleDialog.hide()">
</sqx-rule-wizard>
</ng-container>
<router-outlet></router-outlet> <router-outlet></router-outlet>

24
src/Squidex/app/features/rules/pages/rules/rules-page.component.ts

@ -11,10 +11,10 @@ import { onErrorResumeNext } from 'rxjs/operators';
import { import {
AppsState, AppsState,
DialogModel, DialogModel,
ruleActions,
RuleDto, RuleDto,
RuleElementDto,
RulesService,
RulesState, RulesState,
ruleTriggers,
SchemasState SchemasState
} from '@app/shared'; } from '@app/shared';
@ -24,25 +24,37 @@ import {
templateUrl: './rules-page.component.html' templateUrl: './rules-page.component.html'
}) })
export class RulesPageComponent implements OnInit { export class RulesPageComponent implements OnInit {
public ruleActions = ruleActions;
public ruleTriggers = ruleTriggers;
public addRuleDialog = new DialogModel(); public addRuleDialog = new DialogModel();
public wizardMode = 'Wizard'; public wizardMode = 'Wizard';
public wizardRule: RuleDto | null; public wizardRule: RuleDto | null;
public ruleActions: { [name: string]: RuleElementDto };
public ruleTriggers: { [name: string]: RuleElementDto };
constructor( constructor(
public readonly appsState: AppsState, public readonly appsState: AppsState,
public readonly rulesState: RulesState, public readonly rulesState: RulesState,
public readonly rulesService: RulesService,
public readonly schemasState: SchemasState public readonly schemasState: SchemasState
) { ) {
} }
public ngOnInit() { public ngOnInit() {
this.schemasState.load().pipe(onErrorResumeNext()).subscribe();
this.rulesState.load().pipe(onErrorResumeNext()).subscribe(); this.rulesState.load().pipe(onErrorResumeNext()).subscribe();
this.rulesService.getActions()
.subscribe(actions => {
this.ruleActions = actions;
});
this.rulesService.getTriggers()
.subscribe(triggers => {
this.ruleTriggers = triggers;
});
this.schemasState.load().pipe(onErrorResumeNext()).subscribe();
} }
public reload() { public reload() {

81
src/Squidex/app/shared/services/rules.service.ts

@ -21,41 +21,14 @@ import {
Versioned Versioned
} from '@app/framework'; } from '@app/framework';
export const ruleTriggers: any = { export class RuleElementDto {
'AssetChanged': { constructor(
name: 'Asset changed' public readonly display: string,
}, public readonly description: string,
'ContentChanged': { public readonly link: string
name: 'Content changed' ) {
}
};
export const ruleActions: any = {
'Algolia': {
name: 'Populate Algolia Index'
},
'AzureQueue': {
name: 'Send to Azure Queue'
},
'ElasticSearch': {
name: 'Populate ElasticSearch Index'
},
'Fastly': {
name: 'Purge fastly Cache'
},
'Medium': {
name: 'Post to Medium'
},
'Slack': {
name: 'Send to Slack'
},
'Tweet': {
name: 'Tweet'
},
'Webhook': {
name: 'Send Webhook'
} }
}; }
export class RuleDto extends Model { export class RuleDto extends Model {
constructor( constructor(
@ -129,6 +102,46 @@ export class RulesService {
) { ) {
} }
public getActions(): Observable<{ [name: string]: RuleElementDto }> {
return HTTP.getVersioned<any>(this.http, 'rules/action').pipe(
map(response => {
const items: { [name: string]: any } = response.payload.body;
const result: { [name: string]: RuleElementDto } = {};
for (let key in items) {
if (items.hasOwnProperty(key)) {
const value = items[key];
result[key] = new RuleElementDto(value.display, value.description, value.link);
}
}
return result;
}),
pretifyError('Failed to load Rules. Please reload.'));
}
public getTriggers(): Observable<{ [name: string]: RuleElementDto }> {
return HTTP.getVersioned<any>(this.http, 'rules/triggers').pipe(
map(response => {
const items: { [name: string]: any } = response.payload.body;
const result: { [name: string]: RuleElementDto } = {};
for (let key in items) {
if (items.hasOwnProperty(key)) {
const value = items[key];
result[key] = new RuleElementDto(value.display, value.description, value.link);
}
}
return result;
}),
pretifyError('Failed to load Rules. Please reload.'));
}
public getRules(appName: string): Observable<RuleDto[]> { public getRules(appName: string): Observable<RuleDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`);

Loading…
Cancel
Save