From 9f7c302bcac02edaa9bba6880c89d40a962517d2 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 29 Jan 2018 00:23:21 +0100 Subject: [PATCH 1/2] Slack Action. --- .../Rules/Actions/SlackAction.cs | 52 + .../Rules/IRuleActionVisitor.cs | 2 + .../HandleRules/Actions/SlackActionHandler.cs | 101 ++ .../Actions/WebhookActionHandler.cs | 4 +- .../HandleRules/RuleEventFormatter.cs | 25 + .../HandleRules/RuleService.cs | 7 +- .../Rules/Guards/RuleActionValidator.cs | 12 + .../Rules/Models/Actions/SlackActionDto.cs | 36 + .../Models/Converters/RuleActionDtoFactory.cs | 5 + .../Controllers/Rules/Models/RuleActionDto.cs | 1 + src/Squidex/Config/Domain/ReadServices.cs | 3 + .../app/features/rules/declarations.ts | 1 + src/Squidex/app/features/rules/module.ts | 10 + .../actions/algolia-action.component.html | 2 +- .../rules/actions/slack-action.component.html | 29 + .../rules/actions/slack-action.component.scss | 2 + .../rules/actions/slack-action.component.ts | 55 + .../actions/webhook-action.component.html | 2 +- .../pages/rules/rule-wizard.component.html | 6 + .../pages/rules/rules-page.component.html | 8 + .../app/shared/services/rules.service.ts | 1 + src/Squidex/app/theme/_rules.scss | 33 +- .../app/theme/icomoon/demo-files/demo.css | 2 +- src/Squidex/app/theme/icomoon/demo.html | 658 ++++---- .../app/theme/icomoon/fonts/icomoon.eot | Bin 22144 -> 22428 bytes .../app/theme/icomoon/fonts/icomoon.svg | 1 + .../app/theme/icomoon/fonts/icomoon.ttf | Bin 21980 -> 22264 bytes .../app/theme/icomoon/fonts/icomoon.woff | Bin 22056 -> 22340 bytes src/Squidex/app/theme/icomoon/selection.json | 1468 +++++++++-------- src/Squidex/app/theme/icomoon/style.css | 157 +- .../HandleRules/RuleEventFormatterTests.cs | 39 +- .../Rules/Guards/Actions/SlackActionTests.cs | 47 + 32 files changed, 1613 insertions(+), 1156 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Model/Rules/Actions/SlackAction.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs create mode 100644 src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html create mode 100644 src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss create mode 100644 src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/SlackAction.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/SlackAction.cs new file mode 100644 index 000000000..3e39a2ff7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/SlackAction.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Rules.Actions +{ + [TypeName(nameof(SlackAction))] + public sealed class SlackAction : RuleAction + { + private Uri webhookUrl; + private string text; + + public Uri WebhookUrl + { + get + { + return webhookUrl; + } + set + { + ThrowIfFrozen(); + + webhookUrl = value; + } + } + + public string Text + { + get + { + return text; + } + set + { + ThrowIfFrozen(); + + text = value; + } + } + + public override T Accept(IRuleActionVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs index 44651d1fe..ae9543b0a 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs @@ -13,6 +13,8 @@ namespace Squidex.Domain.Apps.Core.Rules { T Visit(AlgoliaAction action); + T Visit(SlackAction action); + T Visit(WebhookAction action); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs new file mode 100644 index 000000000..9899c5a2e --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs @@ -0,0 +1,101 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Http; + +namespace Squidex.Domain.Apps.Core.HandleRules.Actions +{ + public sealed class SlackActionHandler : RuleActionHandler + { + private static readonly HttpClient Client = new HttpClient { Timeout = TimeSpan.FromSeconds(2) }; + private readonly RuleEventFormatter formatter; + + public SlackActionHandler(RuleEventFormatter formatter) + { + Guard.NotNull(formatter, nameof(formatter)); + + this.formatter = formatter; + } + + protected override (string Description, RuleJobData Data) CreateJob(Envelope @event, string eventName, SlackAction action) + { + var body = CreatePayload(@event, action.Text); + + var ruleDescription = "Send message to slack"; + var ruleData = new RuleJobData + { + ["RequestUrl"] = action.WebhookUrl, + ["RequestBody"] = body + }; + + return (ruleDescription, ruleData); + } + + private JObject CreatePayload(Envelope @event, string text) + { + return new JObject( + new JProperty("text", formatter.FormatString(text, @event))); + } + + public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) + { + var requestBody = job["RequestBody"].ToString(Formatting.Indented); + var request = BuildRequest(job, requestBody); + + HttpResponseMessage response = null; + + try + { + response = await Client.SendAsync(request); + + var responseString = await response.Content.ReadAsStringAsync(); + var requestDump = DumpFormatter.BuildDump(request, response, requestBody, responseString, TimeSpan.Zero, false); + + return (requestDump, null); + } + catch (Exception ex) + { + if (request != null) + { + var requestDump = DumpFormatter.BuildDump(request, response, requestBody, ex.ToString(), TimeSpan.Zero, false); + + return (requestDump, ex); + } + else + { + var requestDump = ex.ToString(); + + return (requestDump, ex); + } + } + } + + private static HttpRequestMessage BuildRequest(Dictionary job, string requestBody) + { + var requestUrl = job["RequestUrl"].Value(); + + var request = new HttpRequestMessage(HttpMethod.Post, requestUrl) + { + Content = new StringContent(requestBody, Encoding.UTF8, "application/json") + }; + + return request; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs index 7b85b1b0e..88baca09f 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs @@ -97,14 +97,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions private static HttpRequestMessage BuildRequest(Dictionary job, string requestBody) { var requestUrl = job["RequestUrl"].Value(); - var requestSignature = job["RequestSignature"].Value(); + var requestSig = job["RequestSignature"].Value(); var request = new HttpRequestMessage(HttpMethod.Post, requestUrl) { Content = new StringContent(requestBody, Encoding.UTF8, "application/json") }; - request.Headers.Add("X-Signature", requestSignature); + request.Headers.Add("X-Signature", requestSig); request.Headers.Add("User-Agent", "Squidex Webhook"); return request; diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index cf7f653a5..9b0d70043 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -27,6 +27,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules private const string SchemaNamePlaceholder = "$SCHEMA_NAME"; private const string TimestampDatePlaceholder = "$TIMESTAMP_DATE"; private const string TimestampDateTimePlaceholder = "$TIMESTAMP_DATETIME"; + private const string ContentActionPlaceholder = "$CONTENT_ACTION"; private static readonly Regex ContentDataPlaceholder = new Regex(@"\$CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); private readonly JsonSerializer serializer; @@ -66,6 +67,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name); } + FormatContentAction(@event, sb); + var result = sb.ToString(); if (@event.Payload is ContentCreated contentCreated && contentCreated.Data != null) @@ -81,6 +84,28 @@ namespace Squidex.Domain.Apps.Core.HandleRules return result; } + private static void FormatContentAction(Envelope @event, StringBuilder sb) + { + switch (@event.Payload) + { + case ContentCreated contentCreated: + sb.Replace(ContentActionPlaceholder, "created"); + break; + + case ContentUpdated contentUpdated: + sb.Replace(ContentActionPlaceholder, "updated"); + break; + + case ContentStatusChanged contentStatusChanged: + sb.Replace(ContentActionPlaceholder, $"set to {contentStatusChanged.Status.ToString().ToLowerInvariant()}"); + break; + + case ContentDeleted contentDeleted: + sb.Replace(ContentActionPlaceholder, "deleted"); + break; + } + } + private static string ReplaceData(NamedContentData data, string text) { return ContentDataPlaceholder.Replace(text, match => diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index 4b1be3d3e..dbae6c8e9 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -87,14 +87,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules @event.Headers.Timestamp() : now; - var eventGuid = - @event.Headers.Contains(CommonHeaders.EventId) ? - @event.Headers.EventId() : - Guid.NewGuid(); - var job = new RuleJob { - JobId = eventGuid, + JobId = Guid.NewGuid(), ActionName = actionName, ActionData = actionData.Data, AppId = appEvent.AppId.Id, diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs index 2e786f76c..b2801f751 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs @@ -46,6 +46,18 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards return Task.FromResult>(errors); } + public Task> Visit(SlackAction action) + { + var errors = new List(); + + if (action.WebhookUrl == null || !action.WebhookUrl.IsAbsoluteUri) + { + errors.Add(new ValidationError("Webhook Url must be specified and absolute.", nameof(action.WebhookUrl))); + } + + return Task.FromResult>(errors); + } + public Task> Visit(WebhookAction action) { var errors = new List(); diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs new file mode 100644 index 000000000..553db29d1 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; +using NJsonSchema.Annotations; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions +{ + [JsonSchema("Slack")] + public sealed class SlackActionDto : RuleActionDto + { + /// + /// The slack webhook url. + /// + [Required] + public Uri WebhookUrl { get; set; } + + /// + /// The text that is sent as message to slack. + /// + public string Text { get; set; } + + public override RuleAction ToAction() + { + return SimpleMapper.Map(this, new SlackAction()); + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs index 22b85de66..1d3ab43b2 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs @@ -30,6 +30,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters return SimpleMapper.Map(action, new AlgoliaActionDto()); } + public RuleActionDto Visit(SlackAction action) + { + return SimpleMapper.Map(action, new SlackActionDto()); + } + public RuleActionDto Visit(WebhookAction action) { return SimpleMapper.Map(action, new WebhookActionDto()); diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs index 6e8591440..21b9cc39b 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs @@ -14,6 +14,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models { [JsonConverter(typeof(JsonInheritanceConverter), "actionType")] [KnownType(typeof(AlgoliaActionDto))] + [KnownType(typeof(SlackActionDto))] [KnownType(typeof(WebhookActionDto))] public abstract class RuleActionDto { diff --git a/src/Squidex/Config/Domain/ReadServices.cs b/src/Squidex/Config/Domain/ReadServices.cs index 4e79bc182..1a24fedd5 100644 --- a/src/Squidex/Config/Domain/ReadServices.cs +++ b/src/Squidex/Config/Domain/ReadServices.cs @@ -98,6 +98,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/src/Squidex/app/features/rules/declarations.ts b/src/Squidex/app/features/rules/declarations.ts index 938940695..815788500 100644 --- a/src/Squidex/app/features/rules/declarations.ts +++ b/src/Squidex/app/features/rules/declarations.ts @@ -6,6 +6,7 @@ */ export * from './pages/rules/actions/algolia-action.component'; +export * from './pages/rules/actions/slack-action.component'; export * from './pages/rules/actions/webhook-action.component'; export * from './pages/rules/triggers/content-changed-trigger.component'; export * from './pages/rules/rule-wizard.component'; diff --git a/src/Squidex/app/features/rules/module.ts b/src/Squidex/app/features/rules/module.ts index 7fe03a653..d9630a213 100644 --- a/src/Squidex/app/features/rules/module.ts +++ b/src/Squidex/app/features/rules/module.ts @@ -9,6 +9,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { + HelpComponent, SqxFrameworkModule, SqxSharedModule } from 'shared'; @@ -19,6 +20,7 @@ import { RuleEventsPageComponent, RulesPageComponent, RuleWizardComponent, + SlackActionComponent, WebhookActionComponent } from './declarations'; @@ -30,6 +32,13 @@ const routes: Routes = [ { path: 'events', component: RuleEventsPageComponent + }, + { + path: 'help', + component: HelpComponent, + data: { + helpPage: '06-integrated/rules' + } } ] } @@ -47,6 +56,7 @@ const routes: Routes = [ RuleEventsPageComponent, RulesPageComponent, RuleWizardComponent, + SlackActionComponent, WebhookActionComponent ] }) diff --git a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html index ee7e590ef..f7e39f603 100644 --- a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html +++ b/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html @@ -36,7 +36,7 @@ - The name of the index. Use $SCHEMA_NAME as a placeholder. + The name of the index. You can use advanced formatting (read help section). diff --git a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html new file mode 100644 index 000000000..4a278f6aa --- /dev/null +++ b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html @@ -0,0 +1,29 @@ +
+
+ + +
+ + + + + + The url to the incoming slack webhook. + +
+
+ +
+ + +
+ + + + + + The text to send to slack. Read the help section for information about advanced formatting. + +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss new file mode 100644 index 000000000..fbb752506 --- /dev/null +++ b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts new file mode 100644 index 000000000..504cee830 --- /dev/null +++ b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts @@ -0,0 +1,55 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'sqx-slack-action', + styleUrls: ['./slack-action.component.scss'], + templateUrl: './slack-action.component.html' +}) +export class SlackActionComponent implements OnInit { + @Input() + public action: any; + + @Output() + public actionChanged = new EventEmitter(); + + public actionFormSubmitted = false; + public actionForm = + this.formBuilder.group({ + webhookUrl: ['', + [ + Validators.required + ]], + text: [''] + }); + + constructor( + private readonly formBuilder: FormBuilder + ) { + } + + public ngOnInit() { + this.action = Object.assign({}, { webhookUrl: '', text: '' }, this.action || {}); + + this.actionFormSubmitted = false; + this.actionForm.reset(); + this.actionForm.setValue(this.action); + } + + public save() { + this.actionFormSubmitted = true; + + if (this.actionForm.valid) { + const action = this.actionForm.value; + + this.actionChanged.emit(action); + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html index 62f2231d5..b643d8cef 100644 --- a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html +++ b/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html @@ -22,7 +22,7 @@ - The shared secret will be used to add a header X-Signature=Sha256(RequestBody + Secret) + The shared secret will be used to add a header X-Signature=Base64(Sha256(RequestBody + Secret)) diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html index 0472353d0..cb1d67acf 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html @@ -68,6 +68,12 @@ (actionChanged)="selectAction($event)"> +
+ + +
+ + + + + + + Click the help icon to show a context specific help page. Go to https://docs.squidex.io for the full documentation. +
diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index e4f50784d..b927d7940 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -26,6 +26,7 @@ export const ruleTriggers: any = { export const ruleActions: any = { 'Algolia': 'Populate Algolia Index', + 'Slack': 'Send to Slack', 'Webhook': 'Send Webhook' }; diff --git a/src/Squidex/app/theme/_rules.scss b/src/Squidex/app/theme/_rules.scss index ef8485eea..6ae054140 100644 --- a/src/Squidex/app/theme/_rules.scss +++ b/src/Squidex/app/theme/_rules.scss @@ -1,26 +1,23 @@ @import '_mixins'; @import '_vars'; -$trigger-content-changed-text: #3389ff; -$trigger-content-changed-icon: #297ff6; +$trigger-content-changed: #3389ff; -$action-webhook-text: #4bb958; -$action-webhook-icon: #47b353; +$action-webhook: #4bb958; +$action-algolia: #0d9bf9; +$action-slack: #5c3a58; -$action-algolia-text: #0d9bf9; -$action-algolia-icon: #1690f5; - -@mixin build-element($text-color, $icon-color) { +@mixin build-element($color) { & { - background: $text-color; + background: $color; } &:hover { - background: $icon-color; + background: darken($color, 5%); } .rule-element-icon { - background: $icon-color; + background: darken($color, 5%); } } @@ -51,13 +48,17 @@ $action-algolia-icon: #1690f5; } .rule-element-ContentChanged { - @include build-element($trigger-content-changed-text, $trigger-content-changed-icon) + @include build-element($trigger-content-changed); } -.rule-element-Webhook { - @include build-element($action-webhook-text, $action-webhook-icon) +.rule-element-Algolia { + @include build-element($action-algolia); } -.rule-element-Algolia { - @include build-element($action-algolia-text, $action-algolia-icon) +.rule-element-Slack { + @include build-element($action-slack); +} + +.rule-element-Webhook { + @include build-element($action-webhook); } \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/demo-files/demo.css b/src/Squidex/app/theme/icomoon/demo-files/demo.css index 5f7c0c6a0..06c37496a 100644 --- a/src/Squidex/app/theme/icomoon/demo-files/demo.css +++ b/src/Squidex/app/theme/icomoon/demo-files/demo.css @@ -147,7 +147,7 @@ p { font-size: 16px; } .fs1 { - font-size: 32px; + font-size: 28px; } .fs2 { font-size: 32px; diff --git a/src/Squidex/app/theme/icomoon/demo.html b/src/Squidex/app/theme/icomoon/demo.html index 0acede9db..b65b4f3e1 100644 --- a/src/Squidex/app/theme/icomoon/demo.html +++ b/src/Squidex/app/theme/icomoon/demo.html @@ -9,20 +9,20 @@
-

Font Name: icomoon (Glyphs: 80)

+

Font Name: icomoon (Glyphs: 81)

-

Grid Size: Unknown

+

Grid Size: 14

- + - icon-action-Algolia + icon-action-Slack
- - + +
liga: @@ -31,14 +31,14 @@
- + - icon-type-Tags + icon-orleans
- - + +
liga: @@ -47,14 +47,14 @@
- + - icon-activity + icon-document-lock
- - + +
liga: @@ -63,14 +63,14 @@
- + - icon-history + icon-document-unpublish
- - + +
liga: @@ -79,14 +79,14 @@
- + - icon-time + icon-angle-down
- - + +
liga: @@ -95,14 +95,14 @@
- + - icon-add + icon-angle-left
- - + +
liga: @@ -111,14 +111,14 @@
- + - icon-plus + icon-angle-right
- - + +
liga: @@ -127,14 +127,14 @@
- + - icon-check-circle + icon-angle-up
- - + +
liga: @@ -143,14 +143,14 @@
- + - icon-check-circle-filled + icon-api
- - + +
liga: @@ -159,14 +159,14 @@
- + - icon-close + icon-assets
- - + +
liga: @@ -175,14 +175,14 @@
- + - icon-type-References + icon-bug
- - + +
liga: @@ -191,14 +191,14 @@
- + - icon-control-Checkbox + icon-caret-down
- - + +
liga: @@ -207,14 +207,14 @@
- + - icon-control-Dropdown + icon-caret-left
- - + +
liga: @@ -223,14 +223,14 @@
- + - icon-control-Input + icon-caret-right
- - + +
liga: @@ -239,14 +239,14 @@
- + - icon-control-Radio + icon-caret-up
- - + +
liga: @@ -255,14 +255,14 @@
- + - icon-control-TextArea + icon-contents
- - + +
liga: @@ -271,14 +271,14 @@
- + - icon-control-Toggle + icon-trigger-ContentChanged
- - + +
liga: @@ -287,14 +287,14 @@
- + - icon-copy + icon-control-Date
- - + +
liga: @@ -303,14 +303,14 @@
- + - icon-dashboard + icon-control-DateTime
- - + +
liga: @@ -319,14 +319,14 @@
- + - icon-delete + icon-control-Markdown
- - + +
liga: @@ -335,14 +335,14 @@
- + - icon-bin + icon-grid
- - + +
liga: @@ -351,14 +351,14 @@
- + - icon-delete-filled + icon-list
- - + +
liga: @@ -367,14 +367,14 @@
- + - icon-document-delete + icon-user-o
- - + +
liga: @@ -383,14 +383,14 @@
- + - icon-document-disable + icon-rules
- - + +
liga: @@ -399,417 +399,417 @@
- + - icon-document-publish + icon-action-Webhook
- - + +
liga:
-
+
+
+

Grid Size: Unknown

+
- + - icon-drag + icon-action-Algolia
- - + +
liga:
-
+
- + - icon-filter + icon-type-Tags
- - + +
liga:
-
+
- + - icon-github + icon-activity
- - + +
liga:
-
+
- + - icon-help + icon-history
- - + +
liga:
-
+
- + - icon-location + icon-time
- - + +
liga:
-
+
- + - icon-control-Map + icon-add
- - + +
liga:
-
+
- + - icon-type-Geolocation + icon-plus
- - + +
liga:
-
+
-
- - + +
liga:
-
+
- + - icon-media + icon-check-circle-filled
- - + +
liga:
-
+
- + - icon-type-Assets + icon-close
- - + +
liga:
-
+
- + - icon-more + icon-type-References
- - + +
liga:
-
+
- + - icon-dots + icon-control-Checkbox
- - + +
liga:
-
+
- + - icon-pencil + icon-control-Dropdown
- - + +
liga:
-
+
- + - icon-reference + icon-control-Input
- - + +
liga:
-
+
- + - icon-schemas + icon-control-Radio
- - + +
liga:
-
+
- + - icon-search + icon-control-TextArea
- - + +
liga:
-
+
- + - icon-settings + icon-control-Toggle
- - + +
liga:
-
+
- + - icon-type-Boolean + icon-copy
- - + +
liga:
-
+
- + - icon-type-DateTime + icon-dashboard
- - + +
liga:
-
+
- + - icon-type-Json + icon-delete
- - + +
liga:
-
+
- + - icon-json + icon-bin
- - + +
liga:
-
+
- + - icon-type-Number + icon-delete-filled
- - + +
liga:
-
+
- + - icon-type-String + icon-document-delete
- - + +
liga:
-
+
- + - icon-user + icon-document-disable
- - + +
liga:
-
-
-

Grid Size: 14

- + - icon-orleans + icon-document-publish
- - + +
liga: @@ -818,14 +818,14 @@
- + - icon-document-lock + icon-drag
- - + +
liga: @@ -834,14 +834,14 @@
- + - icon-document-unpublish + icon-filter
- - + +
liga: @@ -850,14 +850,14 @@
- + - icon-angle-down + icon-github
- - + +
liga: @@ -866,14 +866,14 @@
- + - icon-angle-left + icon-help
- - + +
liga: @@ -882,14 +882,14 @@
- + - icon-angle-right + icon-location
- - + +
liga: @@ -898,14 +898,14 @@
- + - icon-angle-up + icon-control-Map
- - + +
liga: @@ -914,14 +914,14 @@
- + - icon-api + icon-type-Geolocation
- - + +
liga: @@ -930,14 +930,14 @@
- + - icon-assets + icon-logo
- - + +
liga: @@ -946,14 +946,14 @@
- + - icon-bug + icon-media
- - + +
liga: @@ -962,14 +962,14 @@
- + - icon-caret-down + icon-type-Assets
- - + +
liga: @@ -978,14 +978,14 @@
- + - icon-caret-left + icon-more
- - + +
liga: @@ -994,14 +994,14 @@
- + - icon-caret-right + icon-dots
- - + +
liga: @@ -1010,14 +1010,14 @@
- + - icon-caret-up + icon-pencil
- - + +
liga: @@ -1026,14 +1026,14 @@
- + - icon-contents + icon-reference
- - + +
liga: @@ -1042,14 +1042,14 @@
- + - icon-trigger-ContentChanged + icon-schemas
- - + +
liga: @@ -1058,14 +1058,14 @@
- + - icon-control-Date + icon-search
- - + +
liga: @@ -1074,14 +1074,14 @@
- + - icon-control-DateTime + icon-settings
- - + +
liga: @@ -1090,14 +1090,14 @@
- + - icon-control-Markdown + icon-type-Boolean
- - + +
liga: @@ -1106,14 +1106,14 @@
- + - icon-grid + icon-type-DateTime
- - + +
liga: @@ -1122,14 +1122,14 @@
- + - icon-list + icon-type-Json
- - + +
liga: @@ -1138,14 +1138,14 @@
- + - icon-user-o + icon-json
- - + +
liga: @@ -1154,14 +1154,14 @@
- + - icon-rules + icon-type-Number
- - + +
liga: @@ -1170,14 +1170,30 @@
- + - icon-action-Webhook + icon-type-String
- - + + +
+
+ liga: + +
+
+
+
+ + + + icon-user +
+
+ +
liga: diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot index 76dc93458900fab82bb30e622029e1ee9f821cf7..1430ee4632c2d05d5de487434cdde3a839702500 100644 GIT binary patch delta 546 zcmZoz%Q$B}Bio#C28JJD6WPpIxV7%EO>`(1+{wVeum^|}l5-OaZfo4yKk+{y%K!>+GO>m+Fa+}e`6?N?B^8z(f+vA|1E8Le zoc!cOB~eoW28NIpp!}NL#EJrj9L6XHhESk9gF;?nZt6@g&fh?O08o8fL4I)w12a&D zA?yZ7o`IQ}@z~@P#%@O6$sZV%Svv$g`MVcOF!|OqFfeat-p&xikO0(YBql1t#?L4& z#|WgAmDKbZ*^$|Nj6g9{6Ek~8b#^4S9wSi9*hoyC(OeygZN~@{gP3OyWV4#9voohC z8^}l|D=;(aD@p5c%5RjfXJR%`me%G_2xsSKSJso4inNJj=VMpW2eRZKa-4FTwoH2Cso*)-S7GUr(ObXe+r}jsEkw%=xA_hjFs?g2* ILJlzk03G>pc>n+a delta 264 zcmbQUp0QyqBU?il1H+xriEL&pNBw3{cO34 zSquz}6Brl*bTU#CQv{nu7Beseg4CI100lUiSVMvQFF?LZMs7)k=|#bl3=BaMKs^CD z`N@fj&w1uCFa&1+}fw49v`o$0w&Sb~E}+{=lfr+AiS1-?dnR$#=6sKmfCh^bEhF@%%Pl8Ms+MdKj)v q$`FLnljj7RGlp!w670e_StfJ?pSqa(B@Hp4EF%zyY`ztGh!Ftvlt_O7 diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg index a9ff806ec..068a00b7a 100644 --- a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg +++ b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg @@ -84,6 +84,7 @@ + diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf index 8e08f6e9677eebf572192e5b0f417b7b1a8b493f..e279c01a10cc62ec930217d685ce091910347fc9 100644 GIT binary patch delta 571 zcmcb!n(@b4#(D-u1_lOhh6V;^1_S?KeItG$-km^^JwTk0oSRs1TjSP#1_nkMAU`KP zvA6(83jp~YK$;^xr!sBfB-O=0{sabwK$ncv#1z3cUvUP8pesOmvkagBClhNJ14A$m zkgt-FTT)@!A$StVHvsAh$;nT4OjHsz1sWLA0#vXjH?g9CA%`)Ffgu#AfI%TIF*kLl z7w2yvKLDtqtsuX+1n5v82)hB2XJBSNHt|3=qwi!NMrGCx0Z;z!#U+fs^$ZNm+nKjB z#4scPbs33?im>rBipwzqX=NofeMWX840iLJ*76f-swlV>znM`GJC z0>vQanFHCZ=IZRsDar;ilF16pjQUE_I-K$w+K~g^>jqd<>Igf;aG~ V{ZU_}5vQ>ToKQkHONAU_1OQ9n=y)kAw&eoSIA4uO`Yk* z`5VYL0cvO~$S*DdIur;(K^7=5Ff$*Yc%YlnXR;5YGHbhl2Y=V%5=P(6J^lgAGSV~r zj>hxbd}ZKf0qJ14HYr08Mo<0`WX>3}*&x`3adJh-1_^aB^-CIJKv_^|gfK=hfY=NS POp_mm32zP#UCIamt71y2 diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.woff b/src/Squidex/app/theme/icomoon/fonts/icomoon.woff index ca6fd6ece3a10c49f3b409330d2b49e0a3e69e89..d2f1d093690d8440e2442402cd9dd7c9a31c12b1 100644 GIT binary patch delta 627 zcmZ3nhVjTcMv-!VH#Y`G1|SG`Vc-VQVLv7dhKWzq7OCHvoSRs{z`&RR6wLwQ+Zwm_ zrzaMJ#I6AO98fHfo>Q3y6uZN~5a#8Cf2Zw+>#2QSPf8r2nbtt2%gNzPX?;<3%LMPv<8HgL`?;96DxoghXNHcD1dPe zV^m&ZZYof04p3to2+#E5{9TY=TmtkB$O4EuW+021`PgJ0#%@O6$uk(0Svv$g`MVb% zVDzmA%4}!e&Je?p0Mu?ICMv?l&nPa(2&9#j)bttIk=cBVKrvGjGkZpLb|khQBT&rP zNKBs5TpfvR#|RXIm}d@Tvzn{3Gp8sU$VetDFf-~aN$YURZWiCqEmIiOe|J*P4aD0YW|AwUO&XUkR0%1BL2VPFUXsWAg#!Df-g89+gx zm;;cn0>Ye3tf3jXB^5w1pjyTN5H`IicrqtH8K}-LcneU`DiBtD&NDAJu>xpuNCJ?r z0LIyjQF)2EsX(y?pvE>3p6SK;yCA=~1n47>#SnGOKo&Fe@yR@l-HbkyXD}+WwhMUh zcP&1^=)3ube*m+L^bEhF@%%Pl8Ms-1N*NfgP0A32(UV<*%^5>B*95yTPCgQ{K|)(appId, "my-app") }; - var result = sut.FormatString("Name $APP_NAME has id $APP_ID", Envelope.Create(@event).To()); + var result = sut.FormatString("Name $APP_NAME has id $APP_ID", AsEnvelope(@event)); Assert.Equal($"Name my-app has id {appId}", result); } @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules SchemaId = new NamedId(schemaId, "my-schema") }; - var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", Envelope.Create(@event).To()); + var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", AsEnvelope(@event)); Assert.Equal($"Name my-schema has id {schemaId}", result); } @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = sut.FormatString("$CONTENT_DATA.country.iv", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.country.iv", AsEnvelope(@event)); Assert.Equal("UNDEFINED", result); } @@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = sut.FormatString("$CONTENT_DATA.city.de", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.de", AsEnvelope(@event)); Assert.Equal("UNDEFINED", result); } @@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", new JArray())) }; - var result = sut.FormatString("$CONTENT_DATA.city.de.10", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.de.10", AsEnvelope(@event)); Assert.Equal("UNDEFINED", result); } @@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new JProperty("name", "Berlin")))) }; - var result = sut.FormatString("$CONTENT_DATA.city.de.Name", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.de.Name", AsEnvelope(@event)); Assert.Equal("UNDEFINED", result); } @@ -155,7 +155,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); Assert.Equal("Berlin", result); } @@ -171,7 +171,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JValue.CreateNull())) }; - var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); Assert.Equal("UNDEFINED", result); } @@ -187,7 +187,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JValue.CreateUndefined())) }; - var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); Assert.Equal("UNDEFINED", result); } @@ -204,7 +204,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new JProperty("name", "Berlin")))) }; - var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.iv", AsEnvelope(@event)); Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result); } @@ -221,7 +221,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules "Berlin"))) }; - var result = sut.FormatString("$CONTENT_DATA.city.iv.0", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.iv.0", AsEnvelope(@event)); Assert.Equal("Berlin", result); } @@ -238,9 +238,24 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new JProperty("name", "Berlin")))) }; - var result = sut.FormatString("$CONTENT_DATA.city.iv.name", Envelope.Create(@event).To()); + var result = sut.FormatString("$CONTENT_DATA.city.iv.name", AsEnvelope(@event)); Assert.Equal("Berlin", result); } + + [Fact] + public void Should_format_content_action_for_created_when_found() + { + Assert.Equal("created", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentCreated()))); + Assert.Equal("updated", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentUpdated()))); + Assert.Equal("deleted", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentDeleted()))); + + Assert.Equal("set to archived", sut.FormatString("$CONTENT_ACTION", AsEnvelope(new ContentStatusChanged { Status = Status.Archived }))); + } + + private static Envelope AsEnvelope(T @event) where T : AppEvent + { + return Envelope.Create(@event).To(); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs new file mode 100644 index 000000000..b9c63d1c0 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs @@ -0,0 +1,47 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions +{ + public sealed class SlackActionTests + { + [Fact] + public async Task Should_add_error_if_webhook_url_is_null() + { + var action = new SlackAction { WebhookUrl = null }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_add_error_if_webhook_url_is_relative() + { + var action = new SlackAction { WebhookUrl = new Uri("/invalid", UriKind.Relative) }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_webhook_url_is_absolute() + { + var action = new SlackAction { WebhookUrl = new Uri("https://squidex.io", UriKind.Absolute) }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.Empty(errors); + } + } +} From d403b24fc38846f093a375fcb9572b2a3c49192a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 29 Jan 2018 00:31:52 +0100 Subject: [PATCH 2/2] Change to fix build. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cae2f4eb..d7b94d7fc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Current Version 1.1. Roadmap: https://trello.com/b/KakM4F3S/squidex-roadmap ## Contributors -### Core Team +### Core Team and Founders * [Qaisar Ahmad](http://www.qaisarahmad.com/) Interaction Designer, Pakistan * [Sebastian Stehle](https://github.com/SebastianStehle) Software Engineer, Germany (currently Sweden)