From aea1ebc31f57f3f1f20410a40b3fb4c01d9f8384 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 15 Jan 2020 13:06:26 +0100 Subject: [PATCH] Feature/comment trigger (#469) * Comment trigger. * Fixes. * More backend work. * User mentioned trigger finished. * More tests. * Alternative top query parameter. --- .../Actions/Algolia/AlgoliaAction.cs | 2 +- .../Actions/Algolia/AlgoliaActionHandler.cs | 2 +- .../AzureQueue/AzureQueueActionHandler.cs | 2 +- .../Actions/Comment/CommentActionHandler.cs | 2 +- .../Discourse/DiscourseActionHandler.cs | 2 +- .../ElasticSearch/ElasticSearchAction.cs | 2 +- .../ElasticSearchActionHandler.cs | 2 +- .../Actions/Email/EmailActionHandler.cs | 2 +- .../Actions/Fastly/FastlyActionHandler.cs | 2 +- .../Actions/Kafka/KafkaActionHandler.cs | 2 +- .../Actions/Medium/MediumActionHandler.cs | 2 +- .../Notification/NotificationActionHandler.cs | 2 +- .../Actions/Prerender/PrerenderAction.cs | 2 +- .../Prerender/PrerenderActionHandler.cs | 2 +- .../Actions/Slack/SlackAction.cs | 2 +- .../Actions/Slack/SlackActionHandler.cs | 2 +- .../Actions/Twitter/TweetAction.cs | 2 +- .../Actions/Twitter/TweetActionHandler.cs | 2 +- .../Actions/Webhook/WebhookAction.cs | 2 +- .../Actions/Webhook/WebhookActionHandler.cs | 2 +- .../EnrichedEvents/EnrichedAssetEvent.cs | 2 +- .../EnrichedEvents/EnrichedAssetEventType.cs | 2 +- .../EnrichedEvents/EnrichedCommentEvent.cs | 28 ++ .../EnrichedEvents/EnrichedContentEvent.cs | 2 +- .../EnrichedContentEventType.cs | 2 +- .../Rules}/EnrichedEvents/EnrichedEvent.cs | 2 +- .../EnrichedEvents/EnrichedManualEvent.cs | 2 +- .../EnrichedEvents/EnrichedSchemaEvent.cs | 2 +- .../EnrichedEvents/EnrichedSchemaEventBase.cs | 2 +- .../EnrichedEvents/EnrichedSchemaEventType.cs | 2 +- .../EnrichedUsageExceededEvent.cs | 2 +- .../EnrichedEvents/EnrichedUserEventBase.cs | 2 +- .../EnrichedEvents/IEnrichedEntityEvent.cs | 2 +- .../Rules/IRuleTriggerVisitor.cs | 2 + .../Rules/Triggers/CommentTrigger.cs | 22 ++ .../HandleRules/EventEnricher.cs | 2 +- .../HandleRules/IEventEnricher.cs | 2 +- .../HandleRules/IRuleActionHandler.cs | 2 +- .../HandleRules/IRuleTriggerHandler.cs | 5 +- .../HandleRules/RuleActionHandler.cs | 2 +- .../HandleRules/RuleEventFormatter.cs | 35 ++- .../HandleRules/RuleRegistry.cs | 2 +- .../HandleRules/RuleService.cs | 89 +++--- .../HandleRules/RuleTriggerHandler.cs | 26 +- .../Assets/AssetChangedTriggerHandler.cs | 2 +- .../Comments/CommentTriggerHandler.cs | 77 +++++ .../Contents/ContentChangedTriggerHandler.cs | 2 +- .../Rules/Guards/RuleTriggerValidator.cs | 5 + .../Rules/ManualTriggerHandler.cs | 2 +- .../Rules/RuleEnqueuer.cs | 4 +- .../UsageTracking/UsageTriggerHandler.cs | 2 +- .../Schemas/SchemaChangedTriggerHandler.cs | 2 +- .../Squidex.Infrastructure/Queries/Query.cs | 5 + .../Converters/RuleTriggerDtoFactory.cs | 5 + .../Models/Triggers/CommentRuleTriggerDto.cs | 26 ++ .../src/Squidex/Config/Domain/RuleServices.cs | 4 + .../HandleRules/RuleEventFormatterTests.cs | 14 +- .../HandleRules/RuleServiceTests.cs | 121 +++++--- .../Assets/AssetChangedTriggerHandlerTests.cs | 15 +- .../Comments/CommentTriggerHandlerTests.cs | 293 ++++++++++++++++++ .../ContentChangedTriggerHandlerTests.cs | 19 +- .../Rules/ManualTriggerHandlerTests.cs | 7 +- .../Rules/RuleEnqueuerTests.cs | 12 +- .../UsageTracking/UsageTriggerHandlerTests.cs | 11 +- .../SchemaChangedTriggerHandlerTests.cs | 11 +- .../OldTriggers/AssetChangedTrigger.cs | 2 +- .../ContentChangedTriggerSchema.cs | 2 +- frontend/app/features/rules/declarations.ts | 1 + frontend/app/features/rules/module.ts | 2 + .../pages/rules/rule-element.component.scss | 2 +- .../pages/rules/rule-wizard.component.html | 7 + .../triggers/comment-trigger.component.html | 28 ++ .../triggers/comment-trigger.component.scss | 6 + .../triggers/comment-trigger.component.ts | 30 ++ .../contributor-add-form.component.ts | 2 +- .../settings/pages/roles/role.component.ts | 8 +- .../framework/angular/pipes/markdown.pipe.ts | 6 +- .../shared/components/comment.component.html | 16 +- .../shared/components/comment.component.scss | 2 + .../shared/services/clients.service.spec.ts | 4 +- .../services/contributors.service.spec.ts | 2 +- .../app/shared/services/rules.service.spec.ts | 8 +- frontend/app/shared/services/rules.service.ts | 18 +- .../shared/services/schemas.service.spec.ts | 10 +- frontend/app/theme/_bootstrap.scss | 2 +- 85 files changed, 887 insertions(+), 191 deletions(-) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedAssetEvent.cs (95%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedAssetEventType.cs (88%) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedContentEvent.cs (94%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedContentEventType.cs (89%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedEvent.cs (92%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedManualEvent.cs (89%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedSchemaEvent.cs (92%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedSchemaEventBase.cs (90%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedSchemaEventType.cs (89%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedUsageExceededEvent.cs (91%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/EnrichedUserEventBase.cs (91%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/HandleRules => Squidex.Domain.Apps.Core.Model/Rules}/EnrichedEvents/IEnrichedEntityEvent.cs (88%) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/CommentTrigger.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs create mode 100644 backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/CommentRuleTriggerDto.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs create mode 100644 frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.html create mode 100644 frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.scss create mode 100644 frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts diff --git a/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index 5e6ce61b8..79626d1bb 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs @@ -16,7 +16,7 @@ namespace Squidex.Extensions.Actions.Algolia IconImage = "", IconColor = "#0d9bf9", Display = "Populate Algolia index", - Description = "Populate and synchronize indexes in Algolia for full text search.", + Description = "Populate a full text search index in Algolia.", ReadMore = "https://www.algolia.com/")] public sealed class AlgoliaAction : RuleAction { diff --git a/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs index b1ec2c2d0..9a2b12f6a 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs @@ -12,7 +12,7 @@ using Algolia.Search; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using AlgoliaIndex = Algolia.Search.Index; #pragma warning disable IDE0059 // Value assigned to symbol is never used diff --git a/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs index 258e64da7..79e953045 100644 --- a/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Queue; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; namespace Squidex.Extensions.Actions.AzureQueue { diff --git a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs index 96972bce5..2396a0d2e 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs @@ -9,7 +9,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; diff --git a/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs index ad7e1a668..6bec528fe 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs @@ -11,7 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; namespace Squidex.Extensions.Actions.Discourse { diff --git a/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 5446b68c1..11b38a6ac 100644 --- a/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -18,7 +18,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch IconImage = "", IconColor = "#1e5470", Display = "Populate Elasticsearch index", - Description = "Populate and synchronize indexes in ElasticSearch for full text search.", + Description = "Populate a full text search index in ElasticSearch.", ReadMore = "https://www.elastic.co/")] public sealed class ElasticSearchAction : RuleAction { diff --git a/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs index b08a5cfbc..ff0f5b725 100644 --- a/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Elasticsearch.Net; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; #pragma warning disable IDE0059 // Value assigned to symbol is never used diff --git a/backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs index 5c9a84a47..100efaf4d 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs @@ -10,7 +10,7 @@ using System.Net.Mail; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; namespace Squidex.Extensions.Actions.Email { diff --git a/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs index f971a49b7..694e6597b 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs @@ -10,7 +10,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Fastly diff --git a/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs index a2414cb0e..28d01719a 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs @@ -9,7 +9,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; namespace Squidex.Extensions.Actions.Kafka { diff --git a/backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs index 302db3081..9af62802b 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs @@ -11,7 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Json; diff --git a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs index dda2475d7..fabcc4589 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs @@ -9,7 +9,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; diff --git a/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs b/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs index be5708a5f..f0e0a2c2e 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs @@ -16,7 +16,7 @@ namespace Squidex.Extensions.Actions.Prerender IconImage = "", IconColor = "#2c3e50", Display = "Recache URL", - Description = "Recache a certain URL or cache a URL for the first time.", + Description = "Prerender a javascript website for bots.", ReadMore = "https://prerender.io")] public sealed class PrerenderAction : RuleAction { diff --git a/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs index 9a132320f..2b4e83be8 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs @@ -10,7 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; namespace Squidex.Extensions.Actions.Prerender { diff --git a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index eca2cea7c..a67c745f8 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs @@ -18,7 +18,7 @@ namespace Squidex.Extensions.Actions.Slack IconImage = "", IconColor = "#5c3a58", Display = "Send to Slack", - Description = "Create a status update at slack to a channel you define.", + Description = "Create a status update to a slack channel.", ReadMore = "https://slack.com")] public sealed class SlackAction : RuleAction { diff --git a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs index 0815f3baa..ad05ebc38 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs @@ -11,7 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Slack diff --git a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index fc7a69be9..7dcfc4a43 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs @@ -16,7 +16,7 @@ namespace Squidex.Extensions.Actions.Twitter IconImage = "", IconColor = "#1da1f2", Display = "Tweet", - Description = "Create a status update at Tweet to a your user account.", + Description = "Tweet an update with your twitter account.", ReadMore = "https://twitter.com")] public sealed class TweetAction : RuleAction { diff --git a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs index ddd91d9eb..033a70f03 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using CoreTweet; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Twitter diff --git a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index 7ca176004..515826041 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs @@ -17,7 +17,7 @@ namespace Squidex.Extensions.Actions.Webhook IconImage = "", IconColor = "#4bb958", Display = "Send webhook", - Description = "Send events like ContentPublished to your webhook.", + Description = "Invoke HTTP endpoints on a target system.", ReadMore = "https://en.wikipedia.org/wiki/Webhook")] public sealed class WebhookAction : RuleAction { diff --git a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs index ccfa6f308..002d1f4a5 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs @@ -11,7 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Webhook diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs index d77deaa9d..aacddc7fb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs @@ -9,7 +9,7 @@ using System; using NodaTime; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedAssetEvent : EnrichedUserEventBase, IEnrichedEntityEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEventType.cs similarity index 88% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEventType.cs index b4c3ebde0..f3e0d321d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEventType.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public enum EnrichedAssetEventType { diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs new file mode 100644 index 000000000..81b317ee0 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Runtime.Serialization; +using Squidex.Shared.Users; + +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents +{ + public sealed class EnrichedCommentEvent : EnrichedUserEventBase + { + public string Text { get; set; } + + public Uri? Url { get; set; } + + [IgnoreDataMember] + public IUser MentionedUser { get; set; } + + public override long Partition + { + get { return MentionedUser.Id.GetHashCode(); } + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs index c641bc1e4..9ca850775 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs @@ -10,7 +10,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedContentEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEventType.cs similarity index 89% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEventType.cs index 4d6090204..ad6c54f96 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEventType.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public enum EnrichedContentEventType { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs similarity index 92% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs index 00379b772..d1fab9186 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs @@ -9,7 +9,7 @@ using System; using NodaTime; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public abstract class EnrichedEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedManualEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedManualEvent.cs similarity index 89% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedManualEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedManualEvent.cs index 58b358d0d..1165a253e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedManualEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedManualEvent.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedManualEvent : EnrichedEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs similarity index 92% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs index 82735f4d8..5362705c6 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs @@ -7,7 +7,7 @@ using System; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedSchemaEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventBase.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs similarity index 90% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventBase.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs index 25b801671..1092eca84 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventBase.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs @@ -8,7 +8,7 @@ using System; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventType.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventType.cs similarity index 89% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventType.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventType.cs index a996998fb..7d6b3f5cf 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEventType.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventType.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public enum EnrichedSchemaEventType { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUsageExceededEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs similarity index 91% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUsageExceededEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs index 616db38f7..0e0ba7702 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUsageExceededEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedUsageExceededEvent : EnrichedEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEventBase.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs similarity index 91% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEventBase.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs index 8872ef074..6a57cc350 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedUserEventBase.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs @@ -9,7 +9,7 @@ using System.Runtime.Serialization; using Squidex.Infrastructure; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public abstract class EnrichedUserEventBase : EnrichedEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/IEnrichedEntityEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs similarity index 88% rename from backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/IEnrichedEntityEvent.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs index a67e82e07..98f9cfff1 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/IEnrichedEntityEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs @@ -7,7 +7,7 @@ using System; -namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public interface IEnrichedEntityEvent { diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs index a8613b148..ca8913c48 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs @@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Rules T Visit(ContentChangedTriggerV2 trigger); + T Visit(CommentTrigger trigger); + T Visit(ManualTrigger trigger); T Visit(SchemaChangedTrigger trigger); diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/CommentTrigger.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/CommentTrigger.cs new file mode 100644 index 000000000..84273235a --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/CommentTrigger.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Core.Rules.Triggers +{ + [TypeName(nameof(CommentTrigger))] + public sealed class CommentTrigger : RuleTrigger + { + public string Condition { get; set; } + + public override T Accept(IRuleTriggerVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs index 2f82c9480..d9116e485 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs @@ -8,7 +8,7 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IEventEnricher.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IEventEnricher.cs index 1346f9b80..6580265ff 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IEventEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IEventEnricher.cs @@ -6,7 +6,7 @@ // ========================================================================== using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs index 1675abbc9..c81d4e2cb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs @@ -8,8 +8,8 @@ using System; using System.Threading; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; namespace Squidex.Domain.Apps.Core.HandleRules { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs index a74126cc6..49fd6be5b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs @@ -6,9 +6,10 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.EventSourcing; @@ -18,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules { Type TriggerType { get; } - Task CreateEnrichedEventAsync(Envelope @event); + Task> CreateEnrichedEventsAsync(Envelope @event); bool Trigger(EnrichedEvent @event, RuleTrigger trigger); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs index c2b62a052..ab4bbbe24 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs @@ -8,8 +8,8 @@ using System; using System.Threading; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; #pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index 505486f83..ed28780fc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -12,7 +12,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; @@ -50,6 +50,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules AddPattern("CONTENT_ACTION", ContentAction); AddPattern("CONTENT_STATUS", ContentStatus); AddPattern("CONTENT_URL", ContentUrl); + AddPattern("MENTIONED_ID", MentionedId); + AddPattern("MENTIONED_NAME", MentionedName); + AddPattern("MENTIONED_EMAIL", MentionedEmail); AddPattern("SCHEMA_ID", SchemaId); AddPattern("SCHEMA_NAME", SchemaName); AddPattern("TIMESTAMP_DATETIME", TimestampTime); @@ -266,6 +269,36 @@ namespace Squidex.Domain.Apps.Core.HandleRules return Fallback; } + private static string MentionedName(EnrichedEvent @event) + { + if (@event is EnrichedCommentEvent commentEvent) + { + return commentEvent.MentionedUser.DisplayName() ?? Fallback; + } + + return Fallback; + } + + private static string MentionedId(EnrichedEvent @event) + { + if (@event is EnrichedCommentEvent commentEvent) + { + return commentEvent.MentionedUser.Id ?? Fallback; + } + + return Fallback; + } + + private static string MentionedEmail(EnrichedEvent @event) + { + if (@event is EnrichedCommentEvent commentEvent) + { + return commentEvent.MentionedUser.Email ?? Fallback; + } + + return Fallback; + } + private static string CalculateData(NamedContentData data, Match match) { var captures = match.Groups[2].Captures; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs index bb828c223..7bb890577 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs @@ -10,8 +10,8 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index cb2f21066..c95fb00a4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -67,21 +67,23 @@ namespace Squidex.Domain.Apps.Core.HandleRules this.log = log; } - public virtual async Task CreateJobAsync(Rule rule, Guid ruleId, Envelope @event) + public virtual async Task> CreateJobsAsync(Rule rule, Guid ruleId, Envelope @event) { Guard.NotNull(rule); Guard.NotNull(@event); + var result = new List(); + try { if (!rule.IsEnabled) { - return null; + return result; } if (!(@event.Payload is AppEvent)) { - return null; + return result; } var typed = @event.To(); @@ -90,12 +92,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules if (!ruleTriggerHandlers.TryGetValue(rule.Trigger.GetType(), out var triggerHandler)) { - return null; + return result; } if (!ruleActionHandlers.TryGetValue(actionType, out var actionHandler)) { - return null; + return result; } var now = clock.GetCurrentInstant(); @@ -109,59 +111,66 @@ namespace Squidex.Domain.Apps.Core.HandleRules if (eventTime.Plus(Constants.StaleTime) < now) { - return null; + return result; } if (!triggerHandler.Trigger(typed.Payload, rule.Trigger, ruleId)) { - return null; + return result; } var appEventEnvelope = @event.To(); - var enrichedEvent = await triggerHandler.CreateEnrichedEventAsync(appEventEnvelope); - - if (enrichedEvent == null) - { - return null; - } - - await eventEnricher.EnrichAsync(enrichedEvent, typed); + var enrichedEvents = await triggerHandler.CreateEnrichedEventsAsync(appEventEnvelope); - if (!triggerHandler.Trigger(enrichedEvent, rule.Trigger)) + foreach (var enrichedEvent in enrichedEvents) { - return null; + try + { + await eventEnricher.EnrichAsync(enrichedEvent, typed); + + if (!triggerHandler.Trigger(enrichedEvent, rule.Trigger)) + { + continue; + } + + var actionName = typeNameRegistry.GetName(actionType); + var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action); + + var json = jsonSerializer.Serialize(actionData.Data); + + var job = new RuleJob + { + Id = Guid.NewGuid(), + ActionData = json, + ActionName = actionName, + AppId = enrichedEvent.AppId.Id, + Created = now, + Description = actionData.Description, + EventName = enrichedEvent.Name, + ExecutionPartition = enrichedEvent.Partition, + Expires = expires, + RuleId = ruleId + }; + + result.Add(job); + } + catch (Exception ex) + { + log.LogError(ex, w => w + .WriteProperty("action", "createRuleJobFromEvent") + .WriteProperty("status", "Failed")); + } } - - var actionName = typeNameRegistry.GetName(actionType); - var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action); - - var json = jsonSerializer.Serialize(actionData.Data); - - var job = new RuleJob - { - Id = Guid.NewGuid(), - ActionData = json, - ActionName = actionName, - AppId = enrichedEvent.AppId.Id, - Created = now, - Description = actionData.Description, - EventName = enrichedEvent.Name, - ExecutionPartition = enrichedEvent.Partition, - Expires = expires, - RuleId = ruleId - }; - - return job; } catch (Exception ex) { log.LogError(ex, w => w .WriteProperty("action", "createRuleJob") .WriteProperty("status", "Failed")); - - return null; } + + return result; } public virtual async Task<(Result Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs index 5a533e5c1..e9cb5f03f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs @@ -6,9 +6,10 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.EventSourcing; @@ -21,14 +22,28 @@ namespace Squidex.Domain.Apps.Core.HandleRules where TEvent : AppEvent where TEnrichedEvent : EnrichedEvent { + private readonly List emptyEnrichedEvents = new List(); + public Type TriggerType { get { return typeof(TTrigger); } } - async Task IRuleTriggerHandler.CreateEnrichedEventAsync(Envelope @event) + public virtual async Task> CreateEnrichedEventsAsync(Envelope @event) { - return await CreateEnrichedEventAsync(@event.To()); + var enrichedEvent = await CreateEnrichedEventAsync(@event.To()); + + if (enrichedEvent != null) + { + return new List + { + enrichedEvent + }; + } + else + { + return emptyEnrichedEvents; + } } bool IRuleTriggerHandler.Trigger(EnrichedEvent @event, RuleTrigger trigger) @@ -51,7 +66,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules return false; } - protected abstract Task CreateEnrichedEventAsync(Envelope @event); + protected virtual Task CreateEnrichedEventAsync(Envelope @event) + { + return Task.FromResult(null); + } protected abstract bool Trigger(TEnrichedEvent @event, TTrigger trigger); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs index c3fec9472..feae623d1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events.Assets; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs new file mode 100644 index 000000000..6d6ff68d8 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Comments; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; +using Squidex.Shared.Users; + +namespace Squidex.Domain.Apps.Entities.Comments +{ + public sealed class CommentTriggerHandler : RuleTriggerHandler + { + private static readonly List EmptyResult = new List(); + private readonly IScriptEngine scriptEngine; + private readonly IUserResolver userResolver; + + public CommentTriggerHandler(IScriptEngine scriptEngine, IUserResolver userResolver) + { + Guard.NotNull(scriptEngine); + Guard.NotNull(userResolver); + + this.scriptEngine = scriptEngine; + + this.userResolver = userResolver; + } + + public override async Task> CreateEnrichedEventsAsync(Envelope @event) + { + var commentCreated = @event.Payload as CommentCreated; + + if (commentCreated?.Mentions?.Length > 0) + { + var users = await userResolver.QueryManyAsync(commentCreated.Mentions); + + if (users.Count > 0) + { + var result = new List(); + + foreach (var user in users.Values) + { + var enrichedEvent = new EnrichedCommentEvent + { + MentionedUser = user + }; + + enrichedEvent.Name = "UserMentioned"; + + SimpleMapper.Map(commentCreated, enrichedEvent); + + result.Add(enrichedEvent); + } + + return result; + } + } + + return EmptyResult; + } + + protected override bool Trigger(EnrichedCommentEvent @event, CommentTrigger trigger) + { + return string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", @event, trigger.Condition); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs index 306b30b81..56d9b7746 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs @@ -9,7 +9,7 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events.Contents; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs index 64c4a1301..7206d002f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs @@ -36,6 +36,11 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards return action.Accept(visitor); } + public Task> Visit(CommentTrigger trigger) + { + return Task.FromResult(Enumerable.Empty()); + } + public Task> Visit(AssetChangedTriggerV2 trigger) { return Task.FromResult(Enumerable.Empty()); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/ManualTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/ManualTriggerHandler.cs index 53325adb2..618cacb32 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/ManualTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/ManualTriggerHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Events.Rules; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs index 9e3be0d0a..0ade4c788 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs @@ -68,9 +68,9 @@ namespace Squidex.Domain.Apps.Entities.Rules Guard.NotNull(rule); Guard.NotNull(@event); - var job = await ruleService.CreateJobAsync(rule, ruleId, @event); + var jobs = await ruleService.CreateJobsAsync(rule, ruleId, @event); - if (job != null) + foreach (var job in jobs) { await ruleEventRepository.EnqueueAsync(job, job.Created); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs index ae2154284..58650bae2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTriggerHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs index f78db7adb..ebbff50ce 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events; diff --git a/backend/src/Squidex.Infrastructure/Queries/Query.cs b/backend/src/Squidex.Infrastructure/Queries/Query.cs index 25ab0b60a..d9e814d10 100644 --- a/backend/src/Squidex.Infrastructure/Queries/Query.cs +++ b/backend/src/Squidex.Infrastructure/Queries/Query.cs @@ -19,6 +19,11 @@ namespace Squidex.Infrastructure.Queries public long Take { get; set; } = long.MaxValue; + public long Top + { + set { Take = value; } + } + public List Sort { get; set; } = new List(); public override string ToString() diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs index eb7b3095f..7dbbaa3c5 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs @@ -31,6 +31,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters return SimpleMapper.Map(trigger, new AssetChangedRuleTriggerDto()); } + public RuleTriggerDto Visit(CommentTrigger trigger) + { + return SimpleMapper.Map(trigger, new CommentRuleTriggerDto()); + } + public RuleTriggerDto Visit(ManualTrigger trigger) { return SimpleMapper.Map(trigger, new ManualRuleTriggerDto()); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/CommentRuleTriggerDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/CommentRuleTriggerDto.cs new file mode 100644 index 000000000..30650444d --- /dev/null +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/CommentRuleTriggerDto.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers +{ + public class CommentRuleTriggerDto : RuleTriggerDto + { + /// + /// Javascript condition when to trigger. + /// + public string? Condition { get; set; } + + public override RuleTrigger ToTrigger() + { + return SimpleMapper.Map(this, new CommentTrigger()); + } + } +} diff --git a/backend/src/Squidex/Config/Domain/RuleServices.cs b/backend/src/Squidex/Config/Domain/RuleServices.cs index 2cfc9c81a..1fb99ab33 100644 --- a/backend/src/Squidex/Config/Domain/RuleServices.cs +++ b/backend/src/Squidex/Config/Domain/RuleServices.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Comments; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Queries; @@ -33,6 +34,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs index c74a0b05a..aea83312a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs @@ -12,7 +12,7 @@ using FakeItEasy; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; @@ -113,6 +113,18 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result); } + [Theory] + [InlineData("From $MENTIONED_NAME ($MENTIONED_EMAIL, $MENTIONED_ID)")] + [InlineData("Script(`From ${event.mentionedUser.name} (${event.mentionedUser.email}, ${event.mentionedUser.id})`)")] + public void Should_format_email_and_display_name_from_mentioned_user(string script) + { + var @event = new EnrichedCommentEvent { MentionedUser = user }; + + var result = sut.Format(script, @event); + + Assert.Equal("From me (me@email.com, 123)", result); + } + [Theory] [InlineData("From $USER_NAME ($USER_EMAIL, $USER_ID)")] [InlineData("Script(`From ${event.user.name} (${event.user.email}, ${event.user.id})`)")] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs index 630429be8..e1d45e7d7 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs @@ -6,14 +6,16 @@ // ========================================================================== using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FakeItEasy; using Microsoft.Extensions.Options; using NodaTime; 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.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; @@ -97,9 +99,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var @event = Envelope.Create(new ContentCreated()); - var job = await sut.CreateJobAsync(ValidRule().Disable(), ruleId, @event); + var jobs = await sut.CreateJobsAsync(ValidRule().Disable(), ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); A.CallTo(() => ruleTriggerHandler.Trigger(A.Ignored, A.Ignored, ruleId)) .MustNotHaveHappened(); @@ -110,9 +112,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var @event = Envelope.Create(new InvalidEvent()); - var job = await sut.CreateJobAsync(ValidRule(), ruleId, @event); + var jobs = await sut.CreateJobsAsync(ValidRule(), ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); A.CallTo(() => ruleTriggerHandler.Trigger(A.Ignored, A.Ignored, ruleId)) .MustNotHaveHappened(); @@ -123,9 +125,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var @event = Envelope.Create(new ContentCreated()); - var job = await sut.CreateJobAsync(RuleInvalidTrigger(), ruleId, @event); + var jobs = await sut.CreateJobsAsync(RuleInvalidTrigger(), ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); A.CallTo(() => ruleTriggerHandler.Trigger(A.Ignored, A.Ignored, ruleId)) .MustNotHaveHappened(); @@ -136,9 +138,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var @event = Envelope.Create(new ContentCreated()); - var job = await sut.CreateJobAsync(RuleInvalidAction(), ruleId, @event); + var jobs = await sut.CreateJobsAsync(RuleInvalidAction(), ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); A.CallTo(() => ruleTriggerHandler.Trigger(A.Ignored, A.Ignored, ruleId)) .MustNotHaveHappened(); @@ -149,9 +151,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var @event = Envelope.Create(new ContentCreated()).SetTimestamp(clock.GetCurrentInstant().Minus(Duration.FromDays(3))); - var job = await sut.CreateJobAsync(ValidRule(), ruleId, @event); + var jobs = await sut.CreateJobsAsync(ValidRule(), ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); A.CallTo(() => ruleTriggerHandler.Trigger(A.Ignored, A.Ignored, ruleId)) .MustNotHaveHappened(); @@ -167,11 +169,11 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => ruleTriggerHandler.Trigger(@event.Payload, rule.Trigger, ruleId)) .Returns(false); - var job = await sut.CreateJobAsync(rule, ruleId, @event); + var jobs = await sut.CreateJobsAsync(rule, ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); - A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventAsync(A>.Ignored)) + A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(A>.Ignored)) .MustNotHaveHappened(); } @@ -185,12 +187,12 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => ruleTriggerHandler.Trigger(@event.Payload, rule.Trigger, ruleId)) .Returns(true); - A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventAsync(A>.That.Matches(x => x.Payload == @event.Payload))) - .Returns(Task.FromResult(null)); + A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(A>.That.Matches(x => x.Payload == @event.Payload))) + .Returns(new List()); - var job = await sut.CreateJobAsync(rule, ruleId, @event); + var jobs = await sut.CreateJobsAsync(rule, ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); } [Fact] @@ -205,15 +207,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => ruleTriggerHandler.Trigger(@event.Payload, rule.Trigger, ruleId)) .Returns(true); - A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventAsync(A>.That.Matches(x => x.Payload == @event.Payload))) - .Returns(enrichedEvent); + A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(A>.That.Matches(x => x.Payload == @event.Payload))) + .Returns(new List { enrichedEvent }); A.CallTo(() => ruleTriggerHandler.Trigger(enrichedEvent, rule.Trigger)) .Returns(false); - var job = await sut.CreateJobAsync(rule, ruleId, @event); + var jobs = await sut.CreateJobsAsync(rule, ruleId, @event); - Assert.Null(job); + Assert.Empty(jobs); } [Fact] @@ -230,29 +232,64 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => ruleTriggerHandler.Trigger(@event.Payload, rule.Trigger, ruleId)) .Returns(true); + A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(A>.That.Matches(x => x.Payload == @event.Payload))) + .Returns(new List { enrichedEvent }); + A.CallTo(() => ruleTriggerHandler.Trigger(enrichedEvent, rule.Trigger)) .Returns(true); - A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventAsync(A>.That.Matches(x => x.Payload == @event.Payload))) - .Returns(enrichedEvent); - - A.CallTo(() => ruleActionHandler.CreateJobAsync(A.Ignored, rule.Action)) + A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent, rule.Action)) .Returns((actionDescription, new ValidData { Value = 10 })); - var job = (await sut.CreateJobAsync(rule, ruleId, @event))!; + var jobs = (await sut.CreateJobsAsync(rule, ruleId, @event))!; - Assert.Equal(actionData, job.ActionData); - Assert.Equal(actionName, job.ActionName); - Assert.Equal(actionDescription, job.Description); + var job = jobs.Single(); - Assert.Equal(now, job.Created); - Assert.Equal(now.Plus(Duration.FromDays(30)), job.Expires); + AssertJob(now, enrichedEvent, job); - Assert.Equal(enrichedEvent.AppId.Id, job.AppId); + A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent, A>.That.Matches(x => x.Payload == @event.Payload))) + .MustHaveHappened(); + } - Assert.NotEqual(Guid.Empty, job.Id); + [Fact] + public async Task Should_create_multiple_jobs_if_triggered() + { + var now = clock.GetCurrentInstant(); - A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent, A>.That.Matches(x => x.Payload == @event.Payload))) + var rule = ValidRule(); + + var enrichedEvent1 = new EnrichedContentEvent { AppId = appId }; + var enrichedEvent2 = new EnrichedContentEvent { AppId = appId }; + + var @event = Envelope.Create(new ContentCreated()).SetTimestamp(now); + + A.CallTo(() => ruleTriggerHandler.Trigger(@event.Payload, rule.Trigger, ruleId)) + .Returns(true); + + A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(A>.That.Matches(x => x.Payload == @event.Payload))) + .Returns(new List { enrichedEvent1, enrichedEvent2 }); + + A.CallTo(() => ruleTriggerHandler.Trigger(enrichedEvent1, rule.Trigger)) + .Returns(true); + + A.CallTo(() => ruleTriggerHandler.Trigger(enrichedEvent2, rule.Trigger)) + .Returns(true); + + A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent1, rule.Action)) + .Returns((actionDescription, new ValidData { Value = 10 })); + + A.CallTo(() => ruleActionHandler.CreateJobAsync(enrichedEvent2, rule.Action)) + .Returns((actionDescription, new ValidData { Value = 10 })); + + var jobs = (await sut.CreateJobsAsync(rule, ruleId, @event))!; + + AssertJob(now, enrichedEvent1, jobs[0]); + AssertJob(now, enrichedEvent1, jobs[1]); + + A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent1, A>.That.Matches(x => x.Payload == @event.Payload))) + .MustHaveHappened(); + + A.CallTo(() => eventEnricher.EnrichAsync(enrichedEvent2, A>.That.Matches(x => x.Payload == @event.Payload))) .MustHaveHappened(); } @@ -327,5 +364,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { return new Rule(new ContentChangedTriggerV2(), new ValidAction()); } + + private void AssertJob(Instant now, EnrichedContentEvent enrichedEvent, RuleJob job) + { + Assert.Equal(enrichedEvent.AppId.Id, job.AppId); + + Assert.Equal(actionData, job.ActionData); + Assert.Equal(actionName, job.ActionName); + Assert.Equal(actionDescription, job.Description); + + Assert.Equal(now, job.Created); + Assert.Equal(now.Plus(Duration.FromDays(30)), job.Expires); + + Assert.NotEqual(Guid.Empty, job.Id); + } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs index 17a119beb..c9f7003f7 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs @@ -7,10 +7,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events; @@ -50,16 +51,18 @@ namespace Squidex.Domain.Apps.Entities.Assets [Theory] [MemberData(nameof(TestEvents))] - public async Task Should_enrich_events(AssetEvent @event, EnrichedAssetEventType type) + public async Task Should_create_enriched_events(AssetEvent @event, EnrichedAssetEventType type) { var envelope = Envelope.Create(@event).SetEventStreamNumber(12); A.CallTo(() => assetLoader.GetAsync(@event.AssetId, 12)) .Returns(new AssetEntity()); - var result = await sut.CreateEnrichedEventAsync(envelope) as EnrichedAssetEvent; + var result = await sut.CreateEnrichedEventsAsync(envelope); - Assert.Equal(type, result!.Type); + var enrichedEvent = result.Single() as EnrichedAssetEvent; + + Assert.Equal(type, enrichedEvent!.Type); } [Fact] @@ -67,9 +70,9 @@ namespace Squidex.Domain.Apps.Entities.Assets { var envelope = Envelope.Create(new AssetMoved()); - var result = await sut.CreateEnrichedEventAsync(envelope); + var result = await sut.CreateEnrichedEventsAsync(envelope); - Assert.Null(result); + Assert.Empty(result); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs new file mode 100644 index 000000000..f59c59586 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentTriggerHandlerTests.cs @@ -0,0 +1,293 @@ +// ========================================================================== +// 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.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Comments; +using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Shared.Users; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Comments +{ + public class CommentTriggerHandlerTests + { + private readonly IScriptEngine scriptEngine = A.Fake(); + private readonly IUserResolver userResolver = A.Fake(); + private readonly IRuleTriggerHandler sut; + + public CommentTriggerHandlerTests() + { + A.CallTo(() => scriptEngine.Evaluate("event", A.Ignored, "true")) + .Returns(true); + + A.CallTo(() => scriptEngine.Evaluate("event", A.Ignored, "false")) + .Returns(false); + + sut = new CommentTriggerHandler(scriptEngine, userResolver); + } + + [Fact] + public async Task Should_create_enriched_events() + { + var user1 = CreateUser("1"); + var user2 = CreateUser("2"); + + var users = new List { user1, user2 }; + var userIds = users.Select(x => x.Id).ToArray(); + + var envelope = Envelope.Create(new CommentCreated { Mentions = userIds }); + + A.CallTo(() => userResolver.QueryManyAsync(userIds)) + .Returns(users.ToDictionary(x => x.Id)); + + var result = await sut.CreateEnrichedEventsAsync(envelope); + + Assert.Equal(2, result.Count); + + var enrichedEvent1 = result[0] as EnrichedCommentEvent; + var enrichedEvent2 = result[1] as EnrichedCommentEvent; + + Assert.Equal(user1, enrichedEvent1!.MentionedUser); + Assert.Equal(user2, enrichedEvent2!.MentionedUser); + Assert.Equal("UserMentioned", enrichedEvent1.Name); + Assert.Equal("UserMentioned", enrichedEvent2.Name); + } + + [Fact] + public async Task Should_not_create_enriched_events_when_users_cannot_be_resolved() + { + var user1 = CreateUser("1"); + var user2 = CreateUser("2"); + + var users = new List { user1, user2 }; + var userIds = users.Select(x => x.Id).ToArray(); + + var envelope = Envelope.Create(new CommentCreated { Mentions = userIds }); + + var result = await sut.CreateEnrichedEventsAsync(envelope); + + Assert.Empty(result); + } + + [Fact] + public async Task Should_not_create_enriched_events_when_mentions_is_null() + { + var envelope = Envelope.Create(new CommentCreated { Mentions = null }); + + var result = await sut.CreateEnrichedEventsAsync(envelope); + + Assert.Empty(result); + + A.CallTo(() => userResolver.QueryManyAsync(A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_not_create_enriched_events_when_mentions_is_empty() + { + var envelope = Envelope.Create(new CommentCreated { Mentions = new string[0] }); + + var result = await sut.CreateEnrichedEventsAsync(envelope); + + Assert.Empty(result); + + A.CallTo(() => userResolver.QueryManyAsync(A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_skip_udated_event() + { + var envelope = Envelope.Create(new CommentUpdated()); + + var result = await sut.CreateEnrichedEventsAsync(envelope); + + Assert.Empty(result); + + A.CallTo(() => userResolver.QueryManyAsync(A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_skip_deleted_event() + { + var envelope = Envelope.Create(new CommentDeleted()); + + var result = await sut.CreateEnrichedEventsAsync(envelope); + + Assert.Empty(result); + + A.CallTo(() => userResolver.QueryManyAsync(A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_not_trigger_precheck_when_event_type_not_correct() + { + TestForCondition(string.Empty, trigger => + { + var result = sut.Trigger(new ContentCreated(), trigger, Guid.NewGuid()); + + Assert.False(result); + }); + } + + [Fact] + public void Should_trigger_precheck_when_event_type_correct() + { + TestForCondition(string.Empty, trigger => + { + var result = sut.Trigger(new CommentCreated(), trigger, Guid.NewGuid()); + + Assert.True(result); + }); + } + + [Fact] + public void Should_not_trigger_check_when_event_type_not_correct() + { + TestForCondition(string.Empty, trigger => + { + var result = sut.Trigger(new EnrichedContentEvent(), trigger); + + Assert.False(result); + }); + } + + [Fact] + public void Should_trigger_check_when_condition_is_empty() + { + TestForCondition(string.Empty, trigger => + { + var result = sut.Trigger(new EnrichedCommentEvent(), trigger); + + Assert.True(result); + }); + } + + [Fact] + public void Should_trigger_check_when_condition_matchs() + { + TestForCondition("true", trigger => + { + var result = sut.Trigger(new EnrichedCommentEvent(), trigger); + + Assert.True(result); + }); + } + + [Fact] + public void Should_not_trigger_check_when_condition_does_not_matchs() + { + TestForCondition("false", trigger => + { + var result = sut.Trigger(new EnrichedCommentEvent(), trigger); + + Assert.False(result); + }); + } + + [Fact] + public void Should_trigger_check_when_email_is_correct() + { + TestForRealCondition("event.mentionedUser.email == 'sebastian@squidex.io'", (handler, trigger) => + { + var user = CreateUser("1"); + + var result = handler.Trigger(new EnrichedCommentEvent { MentionedUser = user }, trigger); + + Assert.True(result); + }); + } + + [Fact] + public void Should_not_trigger_check_when_email_is_correct() + { + TestForRealCondition("event.mentionedUser.email == 'other@squidex.io'", (handler, trigger) => + { + var user = CreateUser("1"); + + var result = handler.Trigger(new EnrichedCommentEvent { MentionedUser = user }, trigger); + + Assert.False(result); + }); + } + + [Fact] + public void Should_trigger_check_when_text_is_urgent() + { + TestForRealCondition("event.text.indexOf('urgent') >= 0", (handler, trigger) => + { + var text = "Hey man, this is really urgent."; + + var result = handler.Trigger(new EnrichedCommentEvent { Text = text }, trigger); + + Assert.True(result); + }); + } + + [Fact] + public void Should_not_trigger_check_when_text_is_not_urgent() + { + TestForRealCondition("event.text.indexOf('urgent') >= 0", (handler, trigger) => + { + var text = "Hey man, just an information for you."; + + var result = handler.Trigger(new EnrichedCommentEvent { Text = text }, trigger); + + Assert.False(result); + }); + } + + private IUser CreateUser(string id, string email = "sebastian@squidex.io") + { + var user = A.Fake(); + + A.CallTo(() => user.Id).Returns(id); + A.CallTo(() => user.Email).Returns(email); + + return user; + } + + private void TestForRealCondition(string condition, Action action) + { + var trigger = new CommentTrigger { Condition = condition }; + + var handler = new CommentTriggerHandler(new JintScriptEngine(), userResolver); + + action(handler, trigger); + } + + private void TestForCondition(string condition, Action action) + { + var trigger = new CommentTrigger { Condition = condition }; + + action(trigger); + + if (string.IsNullOrWhiteSpace(condition)) + { + A.CallTo(() => scriptEngine.Evaluate("event", A.Ignored, condition)) + .MustNotHaveHappened(); + } + else + { + A.CallTo(() => scriptEngine.Evaluate("event", A.Ignored, condition)) + .MustHaveHappened(); + } + } + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs index ae8924498..d6a99fe2b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs @@ -8,11 +8,12 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events; @@ -59,16 +60,18 @@ namespace Squidex.Domain.Apps.Entities.Contents [Theory] [MemberData(nameof(TestEvents))] - public async Task Should_enrich_events(ContentEvent @event, EnrichedContentEventType type) + public async Task Should_create_enriched_events(ContentEvent @event, EnrichedContentEventType type) { var envelope = Envelope.Create(@event).SetEventStreamNumber(12); A.CallTo(() => contentLoader.GetAsync(@event.ContentId, 12)) .Returns(new ContentEntity { SchemaId = SchemaMatch }); - var result = await sut.CreateEnrichedEventAsync(envelope) as EnrichedContentEvent; + var result = await sut.CreateEnrichedEventsAsync(envelope); - Assert.Equal(type, result!.Type); + var enrichedEvent = result.Single() as EnrichedContentEvent; + + Assert.Equal(type, enrichedEvent!.Type); } [Fact] @@ -87,10 +90,12 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentLoader.GetAsync(@event.ContentId, 11)) .Returns(new ContentEntity { SchemaId = SchemaMatch, Version = 11, Data = dataOld }); - var result = await sut.CreateEnrichedEventAsync(envelope) as EnrichedContentEvent; + var result = await sut.CreateEnrichedEventsAsync(envelope); + + var enrichedEvent = result.Single() as EnrichedContentEvent; - Assert.Same(dataNow, result!.Data); - Assert.Same(dataOld, result!.DataOld); + Assert.Same(dataNow, enrichedEvent!.Data); + Assert.Same(dataOld, enrichedEvent!.DataOld); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/ManualTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/ManualTriggerHandlerTests.cs index 2fcc13a29..700680a6e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/ManualTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/ManualTriggerHandlerTests.cs @@ -5,9 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Rules; @@ -25,9 +26,9 @@ namespace Squidex.Domain.Apps.Entities.Rules { var envelope = Envelope.Create(new RuleManuallyTriggered()); - var result = await sut.CreateEnrichedEventAsync(envelope); + var result = await sut.CreateEnrichedEventsAsync(envelope); - Assert.Equal("Manual", result!.Name); + Assert.Equal("Manual", result.Single().Name); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs index 9eef2daa6..e96859273 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs @@ -74,8 +74,8 @@ namespace Squidex.Domain.Apps.Entities.Rules var job = new RuleJob { Created = now }; - A.CallTo(() => ruleService.CreateJobAsync(rule.RuleDef, rule.Id, @event)) - .Returns(job); + A.CallTo(() => ruleService.CreateJobsAsync(rule.RuleDef, rule.Id, @event)) + .Returns(new List { job }); await sut.Enqueue(rule.RuleDef, rule.Id, @event); @@ -96,11 +96,11 @@ namespace Squidex.Domain.Apps.Entities.Rules A.CallTo(() => appProvider.GetRulesAsync(appId.Id)) .Returns(new List { rule1, rule2 }); - A.CallTo(() => ruleService.CreateJobAsync(rule1.RuleDef, rule1.Id, @event)) - .Returns(job1); + A.CallTo(() => ruleService.CreateJobsAsync(rule1.RuleDef, rule1.Id, @event)) + .Returns(new List { job1 }); - A.CallTo(() => ruleService.CreateJobAsync(rule2.RuleDef, rule2.Id, @event)) - .Returns(Task.FromResult(null)); + A.CallTo(() => ruleService.CreateJobsAsync(rule2.RuleDef, rule2.Id, @event)) + .Returns(new List()); await sut.On(@event); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs index 7be5758ec..94a0f9045 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs @@ -6,9 +6,10 @@ // ========================================================================== using System; +using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; @@ -59,10 +60,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking { var @event = new AppUsageExceeded { CallsCurrent = 80, CallsLimit = 120 }; - var result = await sut.CreateEnrichedEventAsync(Envelope.Create(@event)) as EnrichedUsageExceededEvent; + var result = await sut.CreateEnrichedEventsAsync(Envelope.Create(@event)); - Assert.Equal(@event.CallsCurrent, result!.CallsCurrent); - Assert.Equal(@event.CallsLimit, result!.CallsLimit); + var enrichedEvent = result.Single() as EnrichedUsageExceededEvent; + + Assert.Equal(@event.CallsCurrent, enrichedEvent!.CallsCurrent); + Assert.Equal(@event.CallsLimit, enrichedEvent!.CallsLimit); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs index dbbd928b5..a763fd097 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs @@ -7,10 +7,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events; @@ -50,13 +51,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas [Theory] [MemberData(nameof(TestEvents))] - public async Task Should_enrich_events(SchemaEvent @event, EnrichedSchemaEventType type) + public async Task Should_create_enriched_events(SchemaEvent @event, EnrichedSchemaEventType type) { var envelope = Envelope.Create(@event).SetEventStreamNumber(12); - var result = await sut.CreateEnrichedEventAsync(envelope); + var result = await sut.CreateEnrichedEventsAsync(envelope); - Assert.Equal(type, ((EnrichedSchemaEvent)result!).Type); + var enrichedEvent = result.Single() as EnrichedSchemaEvent; + + Assert.Equal(type, enrichedEvent!.Type); } [Fact] diff --git a/backend/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs b/backend/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs index 9a02a6614..f81f7a038 100644 --- a/backend/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs +++ b/backend/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs @@ -7,8 +7,8 @@ using System; using System.Collections.Generic; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Reflection; diff --git a/backend/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs b/backend/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs index 9128ccb8e..735963124 100644 --- a/backend/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs +++ b/backend/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; namespace Migrate_01.OldTriggers diff --git a/frontend/app/features/rules/declarations.ts b/frontend/app/features/rules/declarations.ts index 05369b922..c8c5e4a06 100644 --- a/frontend/app/features/rules/declarations.ts +++ b/frontend/app/features/rules/declarations.ts @@ -8,6 +8,7 @@ export * from './pages/rules/actions/generic-action.component'; export * from './pages/rules/triggers/asset-changed-trigger.component'; +export * from './pages/rules/triggers/comment-trigger.component'; export * from './pages/rules/triggers/content-changed-trigger.component'; export * from './pages/rules/triggers/schema-changed-trigger.component'; export * from './pages/rules/triggers/usage-trigger.component'; diff --git a/frontend/app/features/rules/module.ts b/frontend/app/features/rules/module.ts index 6c5a534d0..3f6d7307f 100644 --- a/frontend/app/features/rules/module.ts +++ b/frontend/app/features/rules/module.ts @@ -16,6 +16,7 @@ import { import { AssetChangedTriggerComponent, + CommentTriggerComponent, ContentChangedTriggerComponent, GenericActionComponent, RuleComponent, @@ -57,6 +58,7 @@ const routes: Routes = [ ], declarations: [ AssetChangedTriggerComponent, + CommentTriggerComponent, ContentChangedTriggerComponent, GenericActionComponent, RuleComponent, diff --git a/frontend/app/features/rules/pages/rules/rule-element.component.scss b/frontend/app/features/rules/pages/rules/rule-element.component.scss index 3f6119d72..7fcbd6318 100644 --- a/frontend/app/features/rules/pages/rules/rule-element.component.scss +++ b/frontend/app/features/rules/pages/rules/rule-element.component.scss @@ -53,7 +53,7 @@ &-text { font-size: .8rem; - margin-bottom: .25rem; + margin-bottom: .125rem; margin-top: .25rem; } diff --git a/frontend/app/features/rules/pages/rules/rule-wizard.component.html b/frontend/app/features/rules/pages/rules/rule-wizard.component.html index ab3f9c66b..82b9927fe 100644 --- a/frontend/app/features/rules/pages/rules/rule-wizard.component.html +++ b/frontend/app/features/rules/pages/rules/rule-wizard.component.html @@ -63,6 +63,13 @@ [triggerFormSubmitted]="triggerForm.submitted | async"> + + + + +
+ + + + + +
+ +
+

Conditions

+ +

Conditions are javascript expressions that define when to trigger, for example:

+ +
    +
  • + Specific users:
    + + event.mentionedUser.email === 'mail2stehle@gmail.com' +
  • +
  • + Only for text keywords:
    + + event.text.indexOf('urgent') >= 0 +
  • +
+
+ \ No newline at end of file diff --git a/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.scss b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.scss new file mode 100644 index 000000000..6e1eef5ec --- /dev/null +++ b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.scss @@ -0,0 +1,6 @@ +@import '_vars'; +@import '_mixins'; + +textarea { + height: 100px; +} \ No newline at end of file diff --git a/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts new file mode 100644 index 000000000..75d1a8dc9 --- /dev/null +++ b/frontend/app/features/rules/pages/rules/triggers/comment-trigger.component.ts @@ -0,0 +1,30 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; + +@Component({ + selector: 'sqx-comment-trigger', + styleUrls: ['./comment-trigger.component.scss'], + templateUrl: './comment-trigger.component.html' +}) +export class CommentTriggerComponent implements OnInit { + @Input() + public trigger: any; + + @Input() + public triggerForm: FormGroup; + + @Input() + public triggerFormSubmitted = false; + + public ngOnInit() { + this.triggerForm.setControl('condition', + new FormControl(this.trigger.condition || '')); + } +} \ No newline at end of file diff --git a/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts b/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts index 77cb5eba7..b4c94911e 100644 --- a/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts +++ b/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts @@ -70,7 +70,7 @@ export class ContributorAddFormComponent implements OnChanges { } public ngOnChanges() { - this.defaultValue = { role: this.roles[0].name, contributorId: '' }; + this.defaultValue = { role: this.roles ? this.roles[0].name : null, user: '' }; this.assignContributorForm.submitCompleted({ newValue: this.defaultValue }); } diff --git a/frontend/app/features/settings/pages/roles/role.component.ts b/frontend/app/features/settings/pages/roles/role.component.ts index 68605dcff..e14eeb23f 100644 --- a/frontend/app/features/settings/pages/roles/role.component.ts +++ b/frontend/app/features/settings/pages/roles/role.component.ts @@ -18,10 +18,10 @@ import { } from '@app/shared'; const Descriptions = { - 'Developer': 'Can use the API view, edit assets, contents, schemas, rules, workflows and patterns.', - 'Editor': 'Can edit assets and contents and view workflows.', - 'Owner': 'Can do everything, including deleting the app.', - 'Reader': 'Can only read assets and contents.' + Developer: 'Can use the API view, edit assets, contents, schemas, rules, workflows and patterns.', + Editor: 'Can edit assets and contents and view workflows.', + Owner: 'Can do everything, including deleting the app.', + Reader: 'Can only read assets and contents.' }; @Component({ diff --git a/frontend/app/framework/angular/pipes/markdown.pipe.ts b/frontend/app/framework/angular/pipes/markdown.pipe.ts index 39bc5aece..fe6e10804 100644 --- a/frontend/app/framework/angular/pipes/markdown.pipe.ts +++ b/frontend/app/framework/angular/pipes/markdown.pipe.ts @@ -11,7 +11,11 @@ import marked from 'marked'; const renderer = new marked.Renderer(); renderer.link = (href, _, text) => { - return `${text} `; + if (href.startsWith('mailto')) { + return text; + } else { + return `${text} `; + } }; @Pipe({ diff --git a/frontend/app/shared/components/comment.component.html b/frontend/app/shared/components/comment.component.html index 937f5b5c3..09abf1940 100644 --- a/frontend/app/shared/components/comment.component.html +++ b/frontend/app/shared/components/comment.component.html @@ -6,14 +6,6 @@
{{comment.user | sqxUserNameRef}}
- -
@@ -27,4 +19,12 @@
+ + \ No newline at end of file diff --git a/frontend/app/shared/components/comment.component.scss b/frontend/app/shared/components/comment.component.scss index 5d96c7a97..558871db7 100644 --- a/frontend/app/shared/components/comment.component.scss +++ b/frontend/app/shared/components/comment.component.scss @@ -30,7 +30,9 @@ font-size: .9rem; font-weight: normal; line-height: 1.25rem; + margin: 0; margin-bottom: .75rem; + position: relative; } &-message { diff --git a/frontend/app/shared/services/clients.service.spec.ts b/frontend/app/shared/services/clients.service.spec.ts index bd2bea973..10dab0286 100644 --- a/frontend/app/shared/services/clients.service.spec.ts +++ b/frontend/app/shared/services/clients.service.spec.ts @@ -96,7 +96,7 @@ describe('ClientsService', () => { const resource: Resource = { _links: { - 'update': { method: 'PUT', href: '/api/apps/my-app/clients/client1' } + update: { method: 'PUT', href: '/api/apps/my-app/clients/client1' } } }; @@ -125,7 +125,7 @@ describe('ClientsService', () => { const resource: Resource = { _links: { - 'delete': { method: 'DELETE', href: '/api/apps/my-app/clients/client1' } + delete: { method: 'DELETE', href: '/api/apps/my-app/clients/client1' } } }; diff --git a/frontend/app/shared/services/contributors.service.spec.ts b/frontend/app/shared/services/contributors.service.spec.ts index 13e54a101..a6549d1af 100644 --- a/frontend/app/shared/services/contributors.service.spec.ts +++ b/frontend/app/shared/services/contributors.service.spec.ts @@ -93,7 +93,7 @@ describe('ContributorsService', () => { const resource: Resource = { _links: { - 'delete': { method: 'DELETE', href: '/api/apps/my-app/contributors/123' } + delete: { method: 'DELETE', href: '/api/apps/my-app/contributors/123' } } }; diff --git a/frontend/app/shared/services/rules.service.spec.ts b/frontend/app/shared/services/rules.service.spec.ts index 716cf040b..d4c5dc510 100644 --- a/frontend/app/shared/services/rules.service.spec.ts +++ b/frontend/app/shared/services/rules.service.spec.ts @@ -59,7 +59,7 @@ describe('RulesService', () => { expect(req.request.headers.get('If-Match')).toBeNull(); req.flush({ - 'action2': { + action2: { title: 'title2', display: 'display2', description: 'description2', @@ -82,7 +82,7 @@ describe('RulesService', () => { isFormattable: true }] }, - 'action1': { + action1: { title: 'title1', display: 'display1', description: 'description1', @@ -101,8 +101,8 @@ describe('RulesService', () => { ]); expect(actions!).toEqual({ - 'action1': action1, - 'action2': action2 + action1, + action2 }); })); diff --git a/frontend/app/shared/services/rules.service.ts b/frontend/app/shared/services/rules.service.ts index fb9e6b518..dc756feaf 100644 --- a/frontend/app/shared/services/rules.service.ts +++ b/frontend/app/shared/services/rules.service.ts @@ -34,6 +34,7 @@ export type RuleElementMetadataDto = { export type TriggerType = 'AssetChanged' | + 'Comment' | 'ContentChanged' | 'Manual' | 'SchemaChanged' | @@ -42,35 +43,42 @@ export type TriggerType = export type TriggersDto = Record; export const ALL_TRIGGERS: TriggersDto = { - 'AssetChanged': { + AssetChanged: { description: 'For asset changes like uploaded, updated (reuploaded), renamed, deleted...', display: 'Asset changed', iconColor: '#3389ff', iconCode: 'assets', title: 'Asset changed' }, - 'ContentChanged': { + Comment: { + description: 'When a user is mentioned in any comment...', + display: 'User mentioned', + iconColor: '#3389ff', + iconCode: 'comments', + title: 'User mentioned' + }, + ContentChanged: { description: 'For content changes like created, updated, published, unpublished...', display: 'Content changed', iconColor: '#3389ff', iconCode: 'contents', title: 'Content changed' }, - 'Manual': { + Manual: { description: 'To invoke processes manually, for example to update your static site...', display: 'Manually triggered', iconColor: '#3389ff', iconCode: 'play-line', title: 'Manually triggered' }, - 'SchemaChanged': { + SchemaChanged: { description: 'When a schema definition has been created, updated, published or deleted...', display: 'Schema changed', iconColor: '#3389ff', iconCode: 'schemas', title: 'Schema changed' }, - 'Usage': { + Usage: { description: 'When monthly API calls exceed a specified limit for one time a month...', display: 'Usage exceeded', iconColor: '#3389ff', diff --git a/frontend/app/shared/services/schemas.service.spec.ts b/frontend/app/shared/services/schemas.service.spec.ts index 987cea297..3bd387ec3 100644 --- a/frontend/app/shared/services/schemas.service.spec.ts +++ b/frontend/app/shared/services/schemas.service.spec.ts @@ -626,11 +626,13 @@ describe('SchemasService', () => { version: `${id}`, properties: { label: `label${id}${suffix}`, - hints: `hints${id}${suffix}`, - tags: [`tags${id}${suffix}`] + tags: [ + `tags${id}${suffix}` + ], + hints: `hints${id}${suffix}` }, previewUrls: { - 'Default': 'url' + Default: 'url' }, fields: [ { @@ -851,6 +853,6 @@ export function createSchemaDetails(id: number, suffix = '') { update: '' }, { - 'Default': 'url' + Default: 'url' }); } \ No newline at end of file diff --git a/frontend/app/theme/_bootstrap.scss b/frontend/app/theme/_bootstrap.scss index 45faf85b6..b1fb039fa 100644 --- a/frontend/app/theme/_bootstrap.scss +++ b/frontend/app/theme/_bootstrap.scss @@ -177,7 +177,7 @@ a { } .badge { - @include absolute(-.5rem, auto, auto, -.375rem); + @include absolute(-.25rem, auto, auto, -.375rem); background: $color-theme-error; font-size: .75rem; font-weight: normal;