Browse Source

Merge pull request #238 from Squidex/feature-slack-action

Slack Action.
pull/239/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
bb0cad54a7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 52
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/SlackAction.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs
  4. 101
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs
  5. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  6. 25
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  7. 7
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  8. 12
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs
  9. 36
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs
  10. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs
  11. 1
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs
  12. 3
      src/Squidex/Config/Domain/ReadServices.cs
  13. 1
      src/Squidex/app/features/rules/declarations.ts
  14. 10
      src/Squidex/app/features/rules/module.ts
  15. 2
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html
  16. 29
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html
  17. 2
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss
  18. 55
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts
  19. 2
      src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html
  20. 6
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  21. 8
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  22. 1
      src/Squidex/app/shared/services/rules.service.ts
  23. 33
      src/Squidex/app/theme/_rules.scss
  24. 2
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  25. 658
      src/Squidex/app/theme/icomoon/demo.html
  26. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  27. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  28. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  29. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  30. 1468
      src/Squidex/app/theme/icomoon/selection.json
  31. 157
      src/Squidex/app/theme/icomoon/style.css
  32. 39
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  33. 47
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs

2
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)

52
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<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

2
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);
}
}

101
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<SlackAction>
{
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<AppEvent> @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<AppEvent> @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<string, JToken> job, string requestBody)
{
var requestUrl = job["RequestUrl"].Value<string>();
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)
{
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};
return request;
}
}
}

4
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<string, JToken> job, string requestBody)
{
var requestUrl = job["RequestUrl"].Value<string>();
var requestSignature = job["RequestSignature"].Value<string>();
var requestSig = job["RequestSignature"].Value<string>();
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;

25
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<AppEvent> @event, StringBuilder sb)
{
switch (@event.Payload)
{
case ContentCreated contentCreated:
sb.Replace(ContentActionPlaceholder, "created");
break;
case ContentUpdated contentUpdated:
sb.Replace(ContentActionPlaceholder, "updated");
break;
case ContentStatusChanged contentStatusChanged:
sb.Replace(ContentActionPlaceholder, $"set to {contentStatusChanged.Status.ToString().ToLowerInvariant()}");
break;
case ContentDeleted contentDeleted:
sb.Replace(ContentActionPlaceholder, "deleted");
break;
}
}
private static string ReplaceData(NamedContentData data, string text)
{
return ContentDataPlaceholder.Replace(text, match =>

7
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,

12
src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs

@ -46,6 +46,18 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(SlackAction action)
{
var errors = new List<ValidationError>();
if (action.WebhookUrl == null || !action.WebhookUrl.IsAbsoluteUri)
{
errors.Add(new ValidationError("Webhook Url must be specified and absolute.", nameof(action.WebhookUrl)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(WebhookAction action)
{
var errors = new List<ValidationError>();

36
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
{
/// <summary>
/// The slack webhook url.
/// </summary>
[Required]
public Uri WebhookUrl { get; set; }
/// <summary>
/// The text that is sent as message to slack.
/// </summary>
public string Text { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new SlackAction());
}
}
}

5
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());

1
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
{

3
src/Squidex/Config/Domain/ReadServices.cs

@ -98,6 +98,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AlgoliaActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<SlackActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<WebhookActionHandler>()
.As<IRuleActionHandler>();

1
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';

10
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
]
})

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

@ -36,7 +36,7 @@
<input type="text" class="form-control" id="indexName" formControlName="indexName" />
<small class="form-text text-muted">
The name of the index. Use $SCHEMA_NAME as a placeholder.
The name of the index. You can use advanced formatting (read help section).
</small>
</div>
</div>

29
src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html

@ -0,0 +1,29 @@
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">
<label class="col col-2 col-form-label" for="webhookUrl">Webhook Url</label>
<div class="col col-10">
<sqx-control-errors for="webhookUrl" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="webhookUrl" class="form-control" id="webhookUrl" formControlName="webhookUrl" />
<small class="form-text text-muted">
The url to the incoming slack webhook.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-2 col-form-label" for="text">Text</label>
<div class="col col-10">
<sqx-control-errors for="text" [submitted]="actionFormSubmitted"></sqx-control-errors>
<textarea class="form-control" id="text" formControlName="text"></textarea>
<small class="form-text text-muted">
The text to send to slack. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
</form>

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

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

55
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<object>();
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);
}
}
}

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

@ -22,7 +22,7 @@
<input type="text" class="form-control" id="sharedSecret" formControlName="sharedSecret" />
<small class="form-text text-muted">
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))
</small>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -68,6 +68,12 @@
(actionChanged)="selectAction($event)">
</sqx-algolia-action>
</div>
<div *ngSwitchCase="'Slack'">
<sqx-slack-action #actionControl
[action]="action"
(actionChanged)="selectAction($event)">
</sqx-slack-action>
</div>
<div *ngSwitchCase="'Webhook'">
<sqx-webhook-action #actionControl
[action]="action"

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

@ -82,6 +82,14 @@
<a class="panel-link" routerLink="events" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i>
</a>
<a class="panel-link" routerLink="help" routerLinkActive="active" #linkHelp>
<i class="icon-help"></i>
</a>
<sqx-onboarding-tooltip id="help" [for]="linkHelp" position="leftTop" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" target="_blank">https://docs.squidex.io</a> for the full documentation.
</sqx-onboarding-tooltip>
</div>
</div>
</sqx-panel>

1
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'
};

33
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);
}

2
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;

658
src/Squidex/app/theme/icomoon/demo.html

File diff suppressed because it is too large

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -84,6 +84,7 @@
<glyph unicode="&#xe94a;" glyph-name="type-Tags" d="M295.954 137.249h-94.705c-47.353 0-88.786 41.434-88.786 88.786v491.283c0 47.353 41.434 88.786 88.786 88.786h94.705v59.191h-94.705c-82.867 0-147.977-65.11-147.977-147.977v-491.283c0-82.867 65.11-147.977 147.977-147.977h94.705v59.191zM970.728 486.474c-82.867 171.653-201.249 378.821-272.277 378.821h-112.462v-59.191h112.462c35.514-11.838 136.139-177.572 213.087-337.387-76.948-153.896-177.572-325.549-213.087-337.387h-112.462v-59.191h112.462c71.029 0 183.491 207.168 272.277 384.74l5.919 11.838-5.919 17.757zM266.358 622.659v-260.462h59.191v260.462h-59.191zM479.422 622.659v-260.462h59.191v260.462h-59.191z" />
<glyph unicode="&#xe94b;" glyph-name="orleans" d="M512 924.477l-424.96-242.8v-485.64l424.96-242.8 424.96 242.8v485.64l-424.96 242.8zM512 715.337l245.76-138.24v-276.48l-245.76-138.24-245.76 138.24v276.48l245.76 138.24z" />
<glyph unicode="&#xe94c;" glyph-name="action-Algolia" d="M512 933.053c-267.728 0-485.053-217.059-485.053-485.053s217.069-485.053 485.053-485.053c267.983 0 485.053 217.314 485.053 485.308s-217.069 484.797-485.053 484.797zM512 106.504c-188.747 0-341.745 152.988-341.745 341.752 0 188.745 152.998 341.733 341.745 341.733 188.765 0 341.745-152.988 341.745-341.733 0-188.763-152.98-341.752-341.745-341.752zM512 719.478v-254.585c0-7.443 7.957-12.405 14.654-8.939l225.774 117.231c5.042 2.659 6.844 8.986 3.96 13.901-46.936 82.204-133.855 138.576-234.205 142.298z" />
<glyph unicode="&#xe94d;" glyph-name="action-Slack" horiz-adv-x="951" d="M868 507.428c46.857 0 82.857-34.286 82.857-81.143 0-36.571-18.857-62.286-53.143-74.286l-98.286-33.714 32-95.429c2.857-8.571 4-17.714 4-26.857 0-45.143-36.571-82.857-81.714-82.857-36 0-68 22.286-79.429 56.571l-31.429 94.286-177.143-60.571 31.429-93.714c2.857-8.571 4.571-17.714 4.571-26.857 0-44.571-36.571-82.857-82.286-82.857-36 0-67.429 22.286-78.857 56.571l-31.429 93.143-87.429-30.286c-9.143-2.857-18.857-5.143-28.571-5.143-46.286 0-81.143 34.286-81.143 80.571 0 35.429 22.857 67.429 56.571 78.857l89.143 30.286-60 178.857-89.143-30.857c-9.143-2.857-18.286-4.571-27.429-4.571-45.714 0-81.143 34.857-81.143 80.571 0 35.429 22.857 67.429 56.571 78.857l89.714 30.286-30.286 90.857c-2.857 8.571-4.571 17.714-4.571 26.857 0 45.143 36.571 82.857 82.286 82.857 36 0 67.429-22.286 78.857-56.571l30.857-91.429 177.143 60-30.857 91.429c-2.857 8.571-4.571 17.714-4.571 26.857 0 45.143 37.143 82.857 82.286 82.857 36 0 68-22.857 79.429-56.571l30.286-92 92.571 31.429c8 2.286 16 3.429 24.571 3.429 44.571 0 82.857-33.143 82.857-78.857 0-35.429-27.429-65.143-59.429-76l-89.714-30.857 60-180.571 93.714 32c8.571 2.857 17.714 4.571 26.286 4.571zM414.286 357.714l177.143 60-60 180-177.143-61.143z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

1468
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because it is too large

157
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?t1gkof');
src: url('fonts/icomoon.eot?t1gkof#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?t1gkof') format('truetype'),
url('fonts/icomoon.woff?t1gkof') format('woff'),
url('fonts/icomoon.svg?t1gkof#icomoon') format('svg');
src: url('fonts/icomoon.eot?fb43dg');
src: url('fonts/icomoon.eot?fb43dg#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?fb43dg') format('truetype'),
url('fonts/icomoon.woff?fb43dg') format('woff'),
url('fonts/icomoon.svg?fb43dg#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,81 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-action-Slack:before {
content: "\e94d";
}
.icon-orleans:before {
content: "\e94b";
}
.icon-document-lock:before {
content: "\e949";
}
.icon-document-unpublish:before {
content: "\e93f";
}
.icon-angle-down:before {
content: "\e900";
}
.icon-angle-left:before {
content: "\e901";
}
.icon-angle-right:before {
content: "\e931";
}
.icon-angle-up:before {
content: "\e903";
}
.icon-api:before {
content: "\e945";
}
.icon-assets:before {
content: "\e948";
}
.icon-bug:before {
content: "\e93d";
}
.icon-caret-down:before {
content: "\e92c";
}
.icon-caret-left:before {
content: "\e92a";
}
.icon-caret-right:before {
content: "\e929";
}
.icon-caret-up:before {
content: "\e92b";
}
.icon-contents:before {
content: "\e946";
}
.icon-trigger-ContentChanged:before {
content: "\e946";
}
.icon-control-Date:before {
content: "\e936";
}
.icon-control-DateTime:before {
content: "\e937";
}
.icon-control-Markdown:before {
content: "\e938";
}
.icon-grid:before {
content: "\f00a";
}
.icon-list:before {
content: "\f0c9";
}
.icon-user-o:before {
content: "\e932";
}
.icon-rules:before {
content: "\e947";
}
.icon-action-Webhook:before {
content: "\e947";
}
.icon-action-Algolia:before {
content: "\e94c";
}
@ -172,78 +247,6 @@
.icon-user:before {
content: "\e928";
}
.icon-orleans:before {
content: "\e94b";
}
.icon-document-lock:before {
content: "\e949";
}
.icon-document-unpublish:before {
content: "\e93f";
}
.icon-angle-down:before {
content: "\e900";
}
.icon-angle-left:before {
content: "\e901";
}
.icon-angle-right:before {
content: "\e931";
}
.icon-angle-up:before {
content: "\e903";
}
.icon-api:before {
content: "\e945";
}
.icon-assets:before {
content: "\e948";
}
.icon-bug:before {
content: "\e93d";
}
.icon-caret-down:before {
content: "\e92c";
}
.icon-caret-left:before {
content: "\e92a";
}
.icon-caret-right:before {
content: "\e929";
}
.icon-caret-up:before {
content: "\e92b";
}
.icon-contents:before {
content: "\e946";
}
.icon-trigger-ContentChanged:before {
content: "\e946";
}
.icon-control-Date:before {
content: "\e936";
}
.icon-control-DateTime:before {
content: "\e937";
}
.icon-control-Markdown:before {
content: "\e938";
}
.icon-grid:before {
content: "\f00a";
}
.icon-list:before {
content: "\f0c9";
}
.icon-user-o:before {
content: "\e932";
}
.icon-rules:before {
content: "\e947";
}
.icon-action-Webhook:before {
content: "\e947";
}
.icon-bin2:before {
content: "\e902";
}

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

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
AppId = new NamedId<Guid>(appId, "my-app")
};
var result = sut.FormatString("Name $APP_NAME has id $APP_ID", Envelope.Create(@event).To<AppEvent>());
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<Guid>(schemaId, "my-schema")
};
var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", Envelope.Create(@event).To<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent>());
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<AppEvent> AsEnvelope<T>(T @event) where T : AppEvent
{
return Envelope.Create<AppEvent>(@event).To<AppEvent>();
}
}
}

47
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);
}
}
}
Loading…
Cancel
Save