Browse Source

Plugin system for rule actions.

pull/349/head
Sebastian Stehle 7 years ago
parent
commit
82213d5871
  1. 4
      extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs
  2. 1
      extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs
  3. 3
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs
  4. 24
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs
  5. 20
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs
  6. 77
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs
  7. 1
      src/Squidex.Infrastructure/Plugins/IPlugin.cs
  8. 1
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs
  9. 1
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs
  10. 18
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs
  11. 48
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs
  12. 4
      src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  13. 1
      src/Squidex/Config/Domain/RuleServices.cs
  14. 12
      src/Squidex/app/features/rules/declarations.ts
  15. 28
      src/Squidex/app/features/rules/module.ts
  16. 43
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html
  17. 2
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.scss
  18. 42
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts
  19. 29
      src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.html
  20. 2
      src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.scss
  21. 40
      src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts
  22. 99
      src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.html
  23. 6
      src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.scss
  24. 56
      src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.ts
  25. 71
      src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.html
  26. 2
      src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.scss
  27. 46
      src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts
  28. 128
      src/Squidex/app/features/rules/pages/rules/actions/email-action.component.html
  29. 6
      src/Squidex/app/features/rules/pages/rules/actions/email-action.component.scss
  30. 70
      src/Squidex/app/features/rules/pages/rules/actions/email-action.component.ts
  31. 29
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.html
  32. 2
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.scss
  33. 37
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts
  34. 34
      src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html
  35. 6
      src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss
  36. 44
      src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.ts
  37. 96
      src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html
  38. 6
      src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.scss
  39. 54
      src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts
  40. 29
      src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.html
  41. 2
      src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.scss
  42. 37
      src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.ts
  43. 29
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html
  44. 6
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss
  45. 37
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts
  46. 55
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html
  47. 6
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss
  48. 98
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts
  49. 29
      src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html
  50. 2
      src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.scss
  51. 35
      src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts
  52. 10
      src/Squidex/app/features/rules/pages/rules/rule-element.component.html
  53. 9
      src/Squidex/app/features/rules/pages/rules/rule-element.component.scss
  54. 85
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  55. 32
      src/Squidex/app/shared/services/rules.service.spec.ts
  56. 33
      src/Squidex/app/shared/services/rules.service.ts
  57. 166
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs

4
extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs

@ -42,12 +42,10 @@ namespace Squidex.Extensions.Actions.ElasticSearch
[Display(Name = "Username", Description = "The optional username.")]
[DataType(DataType.Text)]
[Formattable]
public string Username { get; set; }
[Display(Name = "Password", Description = "The optional password.")]
[DataType(DataType.Password)]
[Formattable]
[DataType(DataType.Text)]
public string Password { get; set; }
}
}

1
extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs

@ -26,7 +26,6 @@ namespace Squidex.Extensions.Actions.Slack
[Required]
[Display(Name = "Webhook Url", Description = "The slack webhook url.")]
[DataType(DataType.Text)]
[Formattable]
public Uri WebhookUrl { get; set; }
[Required]

3
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core.HandleRules
{
@ -22,5 +23,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public string Display { get; set; }
public string Description { get; set; }
public List<RuleActionProperty> Properties { get; } = new List<RuleActionProperty>();
}
}

24
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.HandleRules
{
public sealed class RuleActionProperty
{
public RuleActionPropertyEditor Editor { get; set; }
public string Name { get; set; }
public string Display { get; set; }
public string Description { get; set; }
public bool IsFormattable { get; set; }
public bool IsRequired { get; set; }
}
}

20
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.HandleRules
{
public enum RuleActionPropertyEditor
{
Checkbox,
Email,
Number,
Password,
Text,
TextArea,
Url
}
}

77
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Squidex.Domain.Apps.Core.Rules;
@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public static class RuleActionRegistry
{
private const string ActionSuffix = "Action";
private const string ActionSuffixV2 = "Action";
private const string ActionSuffixV2 = "ActionV2";
private static readonly HashSet<Type> ActionHandlerTypes = new HashSet<Type>();
private static readonly Dictionary<string, RuleActionDefinition> ActionTypes = new Dictionary<string, RuleActionDefinition>();
@ -70,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var name = GetActionName(actionType);
ActionTypes[name] =
var definition =
new RuleActionDefinition
{
Type = actionType,
@ -81,9 +82,81 @@ namespace Squidex.Domain.Apps.Core.HandleRules
ReadMore = metadata.ReadMore
};
foreach (var property in actionType.GetProperties())
{
if (property.CanRead && property.CanWrite)
{
var actionProperty = new RuleActionProperty { Name = property.Name.ToCamelCase(), Display = property.Name };
var display = property.GetCustomAttribute<DisplayAttribute>();
if (!string.IsNullOrWhiteSpace(display?.Name))
{
actionProperty.Display = display.Name;
}
if (!string.IsNullOrWhiteSpace(display?.Description))
{
actionProperty.Description = display.Description;
}
var type = property.PropertyType;
if ((property.GetCustomAttribute<RequiredAttribute>() != null || (type.IsValueType && !IsNullable(type))) && type != typeof(bool) && type != typeof(bool?))
{
actionProperty.IsRequired = true;
}
if (property.GetCustomAttribute<FormattableAttribute>() != null)
{
actionProperty.IsFormattable = true;
}
var dataType = property.GetCustomAttribute<DataTypeAttribute>()?.DataType;
if (type == typeof(bool) || type == typeof(bool?))
{
actionProperty.Editor = RuleActionPropertyEditor.Checkbox;
}
else if (type == typeof(int) || type == typeof(int?))
{
actionProperty.Editor = RuleActionPropertyEditor.Number;
}
else if (dataType == DataType.Url)
{
actionProperty.Editor = RuleActionPropertyEditor.Url;
}
else if (dataType == DataType.Password)
{
actionProperty.Editor = RuleActionPropertyEditor.Password;
}
else if (dataType == DataType.EmailAddress)
{
actionProperty.Editor = RuleActionPropertyEditor.Email;
}
else if (dataType == DataType.MultilineText)
{
actionProperty.Editor = RuleActionPropertyEditor.TextArea;
}
else
{
actionProperty.Editor = RuleActionPropertyEditor.Text;
}
definition.Properties.Add(actionProperty);
}
}
ActionTypes[name] = definition;
ActionHandlerTypes.Add(actionType.GetCustomAttribute<RuleActionHandlerAttribute>().HandlerType);
}
private static bool IsNullable(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
public static TypeNameRegistry MapRuleActions(this TypeNameRegistry typeNameRegistry)
{
foreach (var actionType in ActionTypes.Values)

1
src/Squidex.Infrastructure/Plugins/IPlugin.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

1
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Extensions.Actions;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{

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

@ -13,7 +13,6 @@ using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Extensions.Actions;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{

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

@ -6,6 +6,9 @@
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{
@ -37,5 +40,20 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// The optional link to the product that is integrated.
/// </summary>
public string ReadMore { get; set; }
/// <summary>
/// The properties.
/// </summary>
[Required]
public RuleElementPropertyDto[] Properties { get; set; }
public static RuleElementDto FromDefinition(RuleActionDefinition definition)
{
var result = SimpleMapper.Map(definition, new RuleElementDto());
result.Properties = definition.Properties.Select(x => SimpleMapper.Map(x, new RuleElementPropertyDto())).ToArray();
return result;
}
}
}

48
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs

@ -0,0 +1,48 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.HandleRules;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{
public sealed class RuleElementPropertyDto
{
/// <summary>
/// The html editor.
/// </summary>
[Required]
public RuleActionPropertyEditor Editor { get; set; }
/// <summary>
/// The name of the editor.
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// The label to use.
/// </summary>
[Required]
public string Display { get; set; }
/// <summary>
/// The optional description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Indicates if the property is formattable.
/// </summary>
public bool IsFormattable { get; set; }
/// <summary>
/// Indicates if the property is required.
/// </summary>
public bool IsRequired { get; set; }
}
}

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

@ -17,10 +17,8 @@ using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Extensions.Actions;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
using Squidex.Shared;
@ -58,7 +56,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[ApiCosts(0)]
public IActionResult GetActions()
{
var response = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => SimpleMapper.Map(x.Value, new RuleElementDto()));
var response = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value));
Response.Headers[HeaderNames.ETag] = RuleActionsEtag;

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

@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.UsageTracking;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Extensions.Actions;
using Squidex.Infrastructure.DependencyInjection;
using Squidex.Infrastructure.EventSourcing;

12
src/Squidex/app/features/rules/declarations.ts

@ -5,17 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/rules/actions/algolia-action.component';
export * from './pages/rules/actions/azure-queue-action.component';
export * from './pages/rules/actions/discourse-action.component';
export * from './pages/rules/actions/elastic-search-action.component';
export * from './pages/rules/actions/email-action.component';
export * from './pages/rules/actions/fastly-action.component';
export * from './pages/rules/actions/medium-action.component';
export * from './pages/rules/actions/prerender-action.component';
export * from './pages/rules/actions/slack-action.component';
export * from './pages/rules/actions/tweet-action.component';
export * from './pages/rules/actions/webhook-action.component';
export * from './pages/rules/actions/generic-action.component';
export * from './pages/rules/triggers/asset-changed-trigger.component';
export * from './pages/rules/triggers/content-changed-trigger.component';

28
src/Squidex/app/features/rules/module.ts

@ -15,26 +15,16 @@ import {
} from '@app/shared';
import {
AlgoliaActionComponent,
AssetChangedTriggerComponent,
AzureQueueActionComponent,
ContentChangedTriggerComponent,
DiscourseActionComponent,
ElasticSearchActionComponent,
EmailActionComponent,
FastlyActionComponent,
MediumActionComponent,
PrerenderActionComponent,
GenericActionComponent,
RuleElementComponent,
RuleEventBadgeClassPipe,
RuleEventsPageComponent,
RulesPageComponent,
RuleWizardComponent,
SchemaChangedTriggerComponent,
SlackActionComponent,
TweetActionComponent,
UsageTriggerComponent,
WebhookActionComponent
UsageTriggerComponent
} from './declarations';
const routes: Routes = [
@ -64,26 +54,16 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
AlgoliaActionComponent,
AssetChangedTriggerComponent,
AzureQueueActionComponent,
ContentChangedTriggerComponent,
DiscourseActionComponent,
EmailActionComponent,
ElasticSearchActionComponent,
FastlyActionComponent,
MediumActionComponent,
PrerenderActionComponent,
GenericActionComponent,
RuleElementComponent,
RuleEventBadgeClassPipe,
RuleEventsPageComponent,
RulesPageComponent,
RuleWizardComponent,
SchemaChangedTriggerComponent,
SlackActionComponent,
TweetActionComponent,
UsageTriggerComponent,
WebhookActionComponent
UsageTriggerComponent
]
})
export class SqxFeatureRulesModule { }

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

@ -1,43 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="appId">App ID</label>
<div class="col-9">
<sqx-control-errors for="appId" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="appId" formControlName="appId" />
<small class="form-text text-muted">
The ID to you algolia application.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="apiKey">Api Key</label>
<div class="col-9">
<sqx-control-errors for="apiKey" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="apiKey" formControlName="apiKey" />
<small class="form-text text-muted">
The API Key to access you algolia app.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="indexName">Index Name</label>
<div class="col-9">
<sqx-control-errors for="indexName" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="indexName" formControlName="indexName" />
<small class="form-text text-muted">
The name of the index. You can use advanced formatting (read help section).
</small>
</div>
</div>
</div>

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

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

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

@ -1,42 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-algolia-action',
styleUrls: ['./algolia-action.component.scss'],
templateUrl: './algolia-action.component.html'
})
export class AlgoliaActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('appId',
new FormControl(this.action.appId || '', [
Validators.required
]));
this.actionForm.setControl('apiKey',
new FormControl(this.action.apiKey || '', [
Validators.required
]));
this.actionForm.setControl('indexName',
new FormControl(this.action.indexName || '$SCHEMA_NAME', [
Validators.required
]));
}
}

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

@ -1,29 +0,0 @@
<form [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="connectionString">Connection String</label>
<div class="col-9">
<sqx-control-errors for="connectionString" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="connectionString" formControlName="connectionString" />
<small class="form-text text-muted">
The connection string to the storage account.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="queue">Queue Name</label>
<div class="col-9">
<sqx-control-errors for="queue" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="queue" formControlName="queue" />
<small class="form-text text-muted">
The name of the queue.
</small>
</div>
</div>
</form>

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

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

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

@ -1,40 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ValidatorsEx } from '@app/shared';
@Component({
selector: 'sqx-azure-queue-action',
styleUrls: ['./azure-queue-action.component.scss'],
templateUrl: './azure-queue-action.component.html'
})
export class AzureQueueActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('connectionString',
new FormControl(this.action.connectionString || '', [
Validators.required
]));
this.actionForm.setControl('queue',
new FormControl(this.action.queue || 'squidex', [
Validators.required,
ValidatorsEx.pattern('[a-z][a-z0-9]{2,}(\-[a-z0-9]+)*', 'Name must be a valid azure queue name.')
]));
}
}

99
src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.html

@ -1,99 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="url">Url</label>
<div class="col-9">
<sqx-control-errors for="url" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="url" class="form-control" id="url" formControlName="url" />
<small class="form-text text-muted">
The url to your discourse server.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="apiKey">Api Key</label>
<div class="col-9">
<sqx-control-errors for="apiKey" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="apiKey" formControlName="apiKey" />
<small class="form-text text-muted">
The api key to authenticate to your discourse server.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="apiUsername">Api Username</label>
<div class="col-9">
<sqx-control-errors for="apiUsername" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="apiUsername" formControlName="apiUsername" />
<small class="form-text text-muted">
The api username to authenticate to your discourse server.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="text">Text</label>
<div class="col-9">
<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 for your topic or post. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="title">Title</label>
<div class="col-9">
<sqx-control-errors for="title" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="title" formControlName="title" />
<small class="form-text text-muted">
The optional title, when you want to create a topic. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="topic">Topic</label>
<div class="col-9">
<sqx-control-errors for="topic" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="number" class="form-control" id="topic" formControlName="topic" />
<small class="form-text text-muted">
The topic id when you want to create a post.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="category">Category</label>
<div class="col-9">
<sqx-control-errors for="category" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="number" class="form-control" id="category" formControlName="category" />
<small class="form-text text-muted">
The category id when you create a topic.
</small>
</div>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.scss

@ -1,6 +0,0 @@
@import '_vars';
@import '_mixins';
textarea {
height: 150px;
}

56
src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.ts

@ -1,56 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-discourse-action',
styleUrls: ['./discourse-action.component.scss'],
templateUrl: './discourse-action.component.html'
})
export class DiscourseActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('url',
new FormControl(this.action.url || '', [
Validators.required
]));
this.actionForm.setControl('apiKey',
new FormControl(this.action.apiKey || '', [
Validators.required
]));
this.actionForm.setControl('apiUsername',
new FormControl(this.action.apiUsername || '', [
Validators.required
]));
this.actionForm.setControl('text',
new FormControl(this.action.text || '', [
Validators.required
]));
this.actionForm.setControl('title',
new FormControl(this.action.title));
this.actionForm.setControl('topic',
new FormControl(this.action.topic));
this.actionForm.setControl('category',
new FormControl(this.action.category));
}
}

71
src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.html

@ -1,71 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="host">Host</label>
<div class="col-9">
<sqx-control-errors for="host" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="host" formControlName="host" placeholder="http://localhost:9200" />
<small class="form-text text-muted">
The url to your elastic search instance.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="username">Username</label>
<div class="col-9">
<sqx-control-errors for="username" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="username" formControlName="username" />
<small class="form-text text-muted">
The username for authentication. Highly recommended.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="username">Password</label>
<div class="col-9">
<sqx-control-errors for="password" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="password" formControlName="password" />
<small class="form-text text-muted">
The password for authentication. Highly recommended.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="indexName">Index Name</label>
<div class="col-9">
<sqx-control-errors for="indexName" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="indexName" formControlName="indexName" />
<small class="form-text text-muted">
The name of the index. You can use advanced formatting (read help section).
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="indexType">Type Name</label>
<div class="col-9">
<sqx-control-errors for="indexType" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="indexType" formControlName="indexType" />
<small class="form-text text-muted">
The name of the type. You can use advanced formatting (read help section).
</small>
</div>
</div>
</div>

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

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

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

@ -1,46 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-elastic-search-action',
styleUrls: ['./elastic-search-action.component.scss'],
templateUrl: './elastic-search-action.component.html'
})
export class ElasticSearchActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('host',
new FormControl(this.action.host || '', [
Validators.required
]));
this.actionForm.setControl('indexName',
new FormControl(this.action.indexName || '$APP_NAME', [
Validators.required
]));
this.actionForm.setControl('indexType',
new FormControl(this.action.indexType || '$SCHEMA_NAME'));
this.actionForm.setControl('username',
new FormControl(this.action.username));
this.actionForm.setControl('password',
new FormControl(this.action.password));
}
}

128
src/Squidex/app/features/rules/pages/rules/actions/email-action.component.html

@ -1,128 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="serverHost">ServerHost</label>
<div class="col-9">
<sqx-control-errors for="serverHost" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="serverHost" formControlName="serverHost" />
<small class="form-text text-muted">
The IP address or host to the SMTP server.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="serverPort">ServerPort</label>
<div class="col-9">
<sqx-control-errors for="serverPort" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="number" class="form-control" id="serverPort" formControlName="serverPort" />
<small class="form-text text-muted">
The port to the SMTP server.
</small>
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="serverUseSsl" formControlName="serverUseSsl" />
<label class="form-check-label" for="serverUseSsl">
ServerUseSsl
</label>
</div>
<small class="form-text text-muted">
Specify whether the SMTP client uses Secure Sockets Layer (SSL) to encrypt the connection.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="serverUsername">ServerUsername</label>
<div class="col-9">
<sqx-control-errors for="serverUsername" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="serverUsername" formControlName="serverUsername" />
<small class="form-text text-muted">
The username for the SMTP server.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="serverPassword">ServerPassword</label>
<div class="col-9">
<sqx-control-errors for="serverPassword" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="password" class="form-control" id="serverPassword" formControlName="serverPassword" />
<small class="form-text text-muted">
The password for the SMTP server.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="messageFrom">MessageFrom</label>
<div class="col-9">
<sqx-control-errors for="messageFrom" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="messageFrom" formControlName="messageFrom" />
<small class="form-text text-muted">
The email sending address. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="messageTo">MessageTo</label>
<div class="col-9">
<sqx-control-errors for="messageTo" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="messageTo" formControlName="messageTo" />
<small class="form-text text-muted">
The email message will be sent to. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="messageSubject">MessageSubject</label>
<div class="col-9">
<sqx-control-errors for="messageSubject" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="messageSubject" formControlName="messageSubject" />
<small class="form-text text-muted">
The subject line for this email message. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="messageBody">MessageBody</label>
<div class="col-9">
<sqx-control-errors for="messageBody" [submitted]="actionFormSubmitted"></sqx-control-errors>
<textarea class="form-control" id="messageBody" formControlName="messageBody"></textarea>
<small class="form-text text-muted">
The message body. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/actions/email-action.component.scss

@ -1,6 +0,0 @@
@import '_vars';
@import '_mixins';
textarea {
height: 250px;
}

70
src/Squidex/app/features/rules/pages/rules/actions/email-action.component.ts

@ -1,70 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-email-action',
styleUrls: ['./email-action.component.scss'],
templateUrl: './email-action.component.html'
})
export class EmailActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('serverHost',
new FormControl(this.action.serverHost || 'smtp.gmail.com', [
Validators.required
]));
this.actionForm.setControl('serverPort',
new FormControl(this.action.serverPort || 465, [
Validators.required
]));
this.actionForm.setControl('serverUseSsl',
new FormControl(this.action.serverUseSsl || true));
this.actionForm.setControl('serverUsername',
new FormControl(this.action.serverUsername || '', [
Validators.required
]));
this.actionForm.setControl('serverPassword',
new FormControl(this.action.serverPassword || '', [
Validators.required
]));
this.actionForm.setControl('messageFrom',
new FormControl(this.action.messageFrom || '', [
Validators.required
]));
this.actionForm.setControl('messageTo',
new FormControl(this.action.messageTo || '', [
Validators.required
]));
this.actionForm.setControl('messageSubject',
new FormControl(this.action.messageSubject || '', [
Validators.required
]));
this.actionForm.setControl('messageBody',
new FormControl(this.action.messageBody || '', [
Validators.required
]));
}
}

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

@ -1,29 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="serviceId">Service ID</label>
<div class="col-9">
<sqx-control-errors for="serviceId" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="serviceId" formControlName="serviceId" />
<small class="form-text text-muted">
The service ID of the fastly account.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="apiKey">Api Key</label>
<div class="col-9">
<sqx-control-errors for="apiKey" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="apiKey" formControlName="apiKey" />
<small class="form-text text-muted">
The API key for the fastly account.
</small>
</div>
</div>
</div>

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

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

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

@ -1,37 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-fastly-action',
styleUrls: ['./fastly-action.component.scss'],
templateUrl: './fastly-action.component.html'
})
export class FastlyActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('serviceId',
new FormControl(this.action.serviceId || '', [
Validators.required
]));
this.actionForm.setControl('apiKey',
new FormControl(this.action.apiKey || '', [
Validators.required
]));
}
}

34
src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html

@ -0,0 +1,34 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row" *ngFor="let property of definition.properties">
<label class="col-3 col-form-label" [for]="property.name">{{property.display}}</label>
<div class="col-9">
<sqx-control-errors [for]="property.name" [submitted]="actionFormSubmitted"></sqx-control-errors>
<div [ngSwitch]="property.editor">
<div *ngSwitchCase="'TextArea'">
<textarea class="form-control" id="{{property.name}}" [formControlName]="property.name"></textarea>
</div>
<div *ngSwitchCase="'Checkbox'">
<div class="form-check">
<input class="form-check-input" type="checkbox" [formControlName]="property.name" />
<label class="form-check-label" [for]="property.name">
{{property.display}}
</label>
</div>
</div>
<div *ngSwitchDefault>
<input type="{{property.editor | lowercase}}" class="form-control" id="{{property.name}}" [formControlName]="property.name" />
</div>
</div>
<small class="form-text text-muted">
{{property.description}}
<ng-container *ngIf="property.isFormattable">
You can use advanced formatting: <a href="https://docs.squidex.io/concepts/rules#3-formatting" sqxExternalLink>Documentation</a>
</ng-container>
</small>
</div>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss

@ -0,0 +1,6 @@
@import '_vars';
@import '_mixins';
.form-check {
padding-top: .5rem;
}

44
src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.ts

@ -0,0 +1,44 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { RuleElementDto } from '@app/shared';
@Component({
selector: 'sqx-generic-action',
styleUrls: ['./generic-action.component.scss'],
templateUrl: './generic-action.component.html'
})
export class GenericActionComponent implements OnInit {
@Input()
public definition: RuleElementDto;
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
for (let property of this.definition.properties) {
const validators = [];
if (property.isRequired) {
validators.push(Validators.required);
}
const control = new FormControl(this.action[property.name] || '', validators);
this.actionForm.setControl(property.name, control);
}
}
}

96
src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html

@ -1,96 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="accessToken">Access Token</label>
<div class="col-9">
<sqx-control-errors for="accessToken" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="accessToken" formControlName="accessToken" />
<small class="form-text text-muted">
The self issued access token. Can be created under <a href="https://medium.com/me/settings" sqxExternalLink>https://medium.com/me/settings</a>.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="title">Title</label>
<div class="col-9">
<sqx-control-errors for="title" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="title" formControlName="title" />
<small class="form-text text-muted">
The title of the post. Note that this title is used for SEO and when rendering the post as a listing.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="content">Content</label>
<div class="col-9">
<sqx-control-errors for="content" [submitted]="actionFormSubmitted"></sqx-control-errors>
<textarea class="form-control" id="content" formControlName="content"></textarea>
<small class="form-text text-muted">
The body of the post, in a valid, semantic, HTML fragment, or Markdown.
</small>
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="isHtml" formControlName="isHtml" />
<label class="form-check-label" for="isHtml">
Is Html content
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="canonicalUrl">Canonical URL</label>
<div class="col-9">
<sqx-control-errors for="canonicalUrl" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="canonicalUrl" formControlName="canonicalUrl" />
<small class="form-text text-muted">
The original home of this content, if it was originally published elsewhere.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="tags">Tags</label>
<div class="col-9">
<sqx-control-errors for="tags" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="tags" formControlName="tags" />
<small class="form-text text-muted">
Optional comma-separated list of tags.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="publicationId">Publication</label>
<div class="col-9">
<sqx-control-errors for="publicationId" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="publicationId" formControlName="publicationId" />
<small class="form-text text-muted">
Optional publication id. Go to <a href="https://medium.com/[PUBLICATION]>?format=json" sqxExternalLink>https://medium.com/[PUBLICATION]?format=json</a> to fetch the id.
</small>
</div>
</div>
</div>

6
src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.scss

@ -1,6 +0,0 @@
@import '_vars';
@import '_mixins';
textarea {
height: 150px;
}

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

@ -1,54 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-medium-action',
styleUrls: ['./medium-action.component.scss'],
templateUrl: './medium-action.component.html'
})
export class MediumActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('accessToken',
new FormControl(this.action.accessToken || '', [
Validators.required
]));
this.actionForm.setControl('title',
new FormControl(this.action.title || '', [
Validators.required
]));
this.actionForm.setControl('content',
new FormControl(this.action.content || '', [
Validators.required
]));
this.actionForm.setControl('canonicalUrl',
new FormControl(this.action.canonicalUrl || ''));
this.actionForm.setControl('tags',
new FormControl(this.action.tags || ''));
this.actionForm.setControl('publicationId',
new FormControl(this.action.publicationId || ''));
this.actionForm.setControl('isHtml',
new FormControl(this.action.isHtml || false));
}
}

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

@ -1,29 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="token">Token</label>
<div class="col-9">
<sqx-control-errors for="token" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" class="form-control" id="token" formControlName="token" />
<small class="form-text text-muted">
The prerender token from your account.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="url">Url</label>
<div class="col-9">
<sqx-control-errors for="url" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="url" class="form-control" id="url" formControlName="url" />
<small class="form-text text-muted">
The url to recache. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
</div>

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

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

37
src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.ts

@ -1,37 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-prerender-action',
styleUrls: ['./prerender-action.component.scss'],
templateUrl: './prerender-action.component.html'
})
export class PrerenderActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('token',
new FormControl(this.action.token || '', [
Validators.required
]));
this.actionForm.setControl('url',
new FormControl(this.action.url || '', [
Validators.required
]));
}
}

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

@ -1,29 +0,0 @@
<form [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="webhookUrl">Webhook Url</label>
<div class="col-9">
<sqx-control-errors for="webhookUrl" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" 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-3 col-form-label" for="text">Text</label>
<div class="col-9">
<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>

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

@ -1,6 +0,0 @@
@import '_vars';
@import '_mixins';
textarea {
height: 150px;
}

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

@ -1,37 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, 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;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('webhookUrl',
new FormControl(this.action.webhookUrl || '', [
Validators.required
]));
this.actionForm.setControl('text',
new FormControl(this.action.text || '', [
Validators.required
]));
}
}

55
src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html

@ -1,55 +0,0 @@
<form [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<div class="col-9 offset-3">
<ng-container *ngIf="!isRedirected">
<button type="button" class="btn btn-twitter" [disabled]="isAuthenticating" (click)="auth()">
Request access token with twitter
</button>
</ng-container>
<ng-container *ngIf="isRedirected">
<form class="form-inline" (ngSubmit)="complete()">
<input class="form-control mr-1" [(ngModel)]="pinCode" [ngModelOptions]="{ standalone: true }" placeholder="Pin" />
<button type="submit" class="btn btn-secondary" [disabled]="!pinCode">
Complete
</button>
</form>
</ng-container>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="accessToken">Access Token</label>
<div class="col-9">
<sqx-control-errors for="accessToken" submitOnly="true" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" readonly class="form-control" id="accessToken" formControlName="accessToken" />
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="accessToken">Access Secret</label>
<div class="col-9">
<sqx-control-errors for="accessSecret" submitOnly="true" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="text" readonly class="form-control" id="accessSecret" formControlName="accessSecret" />
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="text">Text</label>
<div class="col-9">
<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 tweet. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
</form>

6
src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss

@ -1,6 +0,0 @@
@import '_vars';
@import '_mixins';
textarea {
height: 150px;
}

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

@ -1,98 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { HttpClient } from '@angular/common/http';
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { DialogService } from '@app/shared';
@Component({
selector: 'sqx-tweet-action',
styleUrls: ['./tweet-action.component.scss'],
templateUrl: './tweet-action.component.html'
})
export class TweetActionComponent implements OnInit {
private request: any;
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public isAuthenticating = false;
public isRedirected = false;
public pinCode: string;
constructor(
private readonly dialogs: DialogService,
private readonly httpClient: HttpClient
) {
}
public ngOnInit() {
this.actionForm.setControl('accessToken',
new FormControl(this.action.accessToken || '', [
Validators.required
]));
this.actionForm.setControl('accessSecret',
new FormControl(this.action.accessSecret || '', [
Validators.required
]));
this.actionForm.setControl('text',
new FormControl(this.action.text || '', [
Validators.required,
Validators.maxLength(280)
]));
}
public auth() {
this.isAuthenticating = true;
this.httpClient.get('api/rules/twitter/auth')
.subscribe((response: any) => {
this.request = {
requestToken: response.requestToken,
requestTokenSecret: response.requestTokenSecret
};
this.isAuthenticating = false;
this.isRedirected = true;
window.open(response.authorizeUri, '_blank');
}, () => {
this.dialogs.notifyError('Failed to authenticate with twitter.');
this.isAuthenticating = false;
this.isRedirected = false;
});
}
public complete() {
this.request.pinCode = this.pinCode;
this.httpClient.post('api/rules/twitter/token', this.request)
.subscribe((response: any) => {
this.actionForm.get('accessToken')!.setValue(response.accessToken);
this.actionForm.get('accessSecret')!.setValue(response.accessTokenSecret);
this.isRedirected = false;
}, () => {
this.dialogs.notifyError('Failed to request access token.');
this.isAuthenticating = false;
this.isRedirected = false;
});
}
}

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

@ -1,29 +0,0 @@
<div [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col-3 col-form-label" for="url">Url</label>
<div class="col-9">
<sqx-control-errors for="url" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="url" class="form-control" id="url" formControlName="url" />
<small class="form-text text-muted">
The url where the events will be sent to.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="sharedSecret">Secret</label>
<div class="col-9">
<sqx-control-errors for="sharedSecret" [submitted]="actionFormSubmitted"></sqx-control-errors>
<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=Base64(Sha256(RequestBody + Secret))
</small>
</div>
</div>
</div>

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

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

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

@ -1,35 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-webhook-action',
styleUrls: ['./webhook-action.component.scss'],
templateUrl: './webhook-action.component.html'
})
export class WebhookActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('url',
new FormControl(this.action.url || '', [
Validators.required
]));
this.actionForm.setControl('sharedSecret',
new FormControl(this.action.sharedSecret || ''));
}
}

10
src/Squidex/app/features/rules/pages/rules/rule-element.component.html

@ -1,16 +1,14 @@
<ng-container *ngIf="isSmall; else large">
<div class="row no-gutters align-items-center small" *ngIf="element" [style.background]="element.iconColor" [sqxHoverBackground]="element.iconColor | sqxDarken:5">
<div class="col-auto small-icon" [style.background]="element.iconColor | sqxDarken:5">
<div class="small" *ngIf="element" [style.background]="element.iconColor" [sqxHoverBackground]="element.iconColor | sqxDarken:5">
<div class="small-icon" [style.background]="element.iconColor | sqxDarken:5">
<i *ngIf="element.iconCode; else svgIcon" class="icon icon-{{element.iconCode}}"></i>
<ng-template #svgIcon>
<i class="svg-icon" [innerHtml]="element.iconImage | sqxSafeHtml"></i>
</ng-template>
</div>
<div class="col align-items-center">
<div class="small-text">
{{element.display}}
</div>
<div class="small-text">
{{element.display}}
</div>
</div>
</ng-container>

9
src/Squidex/app/features/rules/pages/rules/rule-element.component.scss

@ -5,9 +5,12 @@
& {
@include transition(background-color .4s ease);
cursor: pointer;
height: 3rem;
position: relative;
}
&-text {
@include absolute(0, 0, 0, 3rem);
@include truncate;
color: $color-dark-foreground;
line-height: 3rem;
@ -17,16 +20,14 @@
}
&-icon {
@include absolute(0, auto, 0, 0);
color: $color-dark-foreground;
line-height: 3.2rem;
font-size: 1.2rem;
font-weight: normal;
padding: 0 .8rem;
}
.col {
height: 3rem;
}
.icon {
font-size: 20px;
}

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

@ -79,85 +79,12 @@
<form [formGroup]="actionForm.form" (ngSubmit)="saveAction()">
<h3 class="wizard-title">{{ruleActions[actionType].display}}</h3>
<ng-container [ngSwitch]="actionType">
<ng-container *ngSwitchCase="'Algolia'">
<sqx-algolia-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-algolia-action>
</ng-container>
<ng-container *ngSwitchCase="'AzureQueue'">
<sqx-azure-queue-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-azure-queue-action>
</ng-container>
<ng-container *ngSwitchCase="'Discourse'">
<sqx-discourse-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-discourse-action>
</ng-container>
<ng-container *ngSwitchCase="'ElasticSearch'">
<sqx-elastic-search-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-elastic-search-action>
</ng-container>
<ng-container *ngSwitchCase="'Email'">
<sqx-email-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-email-action>
</ng-container>
<ng-container *ngSwitchCase="'Fastly'">
<sqx-fastly-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-fastly-action>
</ng-container>
<ng-container *ngSwitchCase="'Medium'">
<sqx-medium-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-medium-action>
</ng-container>
<ng-container *ngSwitchCase="'Prerender'">
<sqx-prerender-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-prerender-action>
</ng-container>
<ng-container *ngSwitchCase="'Slack'">
<sqx-slack-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-slack-action>
</ng-container>
<ng-container *ngSwitchCase="'Tweet'">
<sqx-tweet-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-tweet-action>
</ng-container>
<ng-container *ngSwitchCase="'Webhook'">
<sqx-webhook-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-webhook-action>
</ng-container>
</ng-container>
<sqx-generic-action
[definition]="ruleActions[actionType]"
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-generic-action>
</form>
</ng-container>
</ng-container>

32
src/Squidex/app/shared/services/rules.service.spec.ts

@ -22,6 +22,7 @@ import {
UpdateRuleDto,
Version
} from './../';
import { RuleElementPropertyDto } from './rules.service';
describe('RulesService', () => {
const now = DateTime.now();
@ -65,20 +66,43 @@ describe('RulesService', () => {
description: 'description2',
iconColor: '#222',
iconImage: '<svg path="2" />',
readMore: 'link2'
readMore: 'link2',
properties: [{
name: 'property1',
editor: 'Editor1',
display: 'Display1',
description: 'Description1',
isRequired: true,
isFormattable: false
}, {
name: 'property2',
editor: 'Editor2',
display: 'Display2',
description: 'Description2',
isRequired: false,
isFormattable: true
}]
},
'action1': {
display: 'display1',
description: 'description1',
iconColor: '#111',
iconImage: '<svg path="1" />',
readMore: 'link1'
readMore: 'link1',
properties: []
}
});
const action1 = new RuleElementDto('display1', 'description1', '#111', '<svg path="1" />', null, 'link1', []);
const action2 = new RuleElementDto('display2', 'description2', '#222', '<svg path="2" />', null, 'link2', [
new RuleElementPropertyDto('property1', 'Editor1', 'Display1', 'Description1', false, true),
new RuleElementPropertyDto('property2', 'Editor2', 'Display2', 'Description2', true, false)
]);
expect(actions!).toEqual({
'action1': new RuleElementDto('display1', 'description1', '#111', '<svg path="1" />', null, 'link1'),
'action2': new RuleElementDto('display2', 'description2', '#222', '<svg path="2" />', null, 'link2')
'action1': action1,
'action2': action2
});
}));

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

@ -54,7 +54,20 @@ export class RuleElementDto {
public readonly iconColor: string,
public readonly iconImage: string,
public readonly iconCode: string | null,
public readonly readMore: string
public readonly readMore: string,
public readonly properties: RuleElementPropertyDto[]
) {
}
}
export class RuleElementPropertyDto {
constructor(
public readonly name: string,
public readonly editor: string,
public readonly display: string,
public readonly description: string,
public readonly isFormattable: boolean,
public readonly isRequired: boolean
) {
}
}
@ -147,7 +160,23 @@ export class RulesService {
for (let key of Object.keys(items).sort()) {
const value = items[key];
result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, null, value.readMore);
const properties = value.properties.map((property: any) =>
new RuleElementPropertyDto(
property.name,
property.editor,
property.display,
property.description,
property.isFormattable,
property.isRequired
));
result[key] = new RuleElementDto(
value.display,
value.description,
value.iconColor,
value.iconImage, null,
value.readMore,
properties);
}
return result;

166
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs

@ -0,0 +1,166 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using FluentAssertions;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
public class RuleElementRegistry
{
private abstract class MyRuleActionHandler : RuleActionHandler<MyRuleAction, string>
{
protected MyRuleActionHandler(RuleEventFormatter formatter)
: base(formatter)
{
}
}
[RuleActionHandler(typeof(MyRuleActionHandler))]
[RuleAction(
IconImage = "<svg></svg>",
IconColor = "#1e5470",
Display = "Action display",
Description = "Action description.",
ReadMore = "https://www.readmore.com/")]
public sealed class MyRuleAction : RuleAction
{
[Required]
[Display(Name = "Url Name", Description = "Url Description")]
[DataType(DataType.Url)]
[Formattable]
public Uri Url { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DataType(DataType.Text)]
public string Text { get; set; }
[DataType(DataType.MultilineText)]
public string TextMultiline { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
public bool Boolean { get; set; }
public bool? BooleanOptional { get; set; }
public int Number { get; set; }
public int? NumberOptional { get; set; }
}
[Fact]
public void Should_create_definition()
{
var expected = new RuleActionDefinition
{
Type = typeof(MyRuleAction),
IconImage = "<svg></svg>",
IconColor = "#1e5470",
Display = "Action display",
Description = "Action description.",
ReadMore = "https://www.readmore.com/"
};
expected.Properties.Add(new RuleActionProperty
{
Name = "url",
Display = "Url Name",
Description = "Url Description",
Editor = RuleActionPropertyEditor.Url,
IsFormattable = true,
IsRequired = true
});
expected.Properties.Add(new RuleActionProperty
{
Name = "email",
Display = "Email",
Description = null,
Editor = RuleActionPropertyEditor.Email,
IsRequired = false
});
expected.Properties.Add(new RuleActionProperty
{
Name = "text",
Display = "Text",
Description = null,
Editor = RuleActionPropertyEditor.Text,
IsRequired = false
});
expected.Properties.Add(new RuleActionProperty
{
Name = "textMultiline",
Display = "TextMultiline",
Description = null,
Editor = RuleActionPropertyEditor.TextArea,
IsRequired = false
});
expected.Properties.Add(new RuleActionProperty
{
Name = "password",
Display = "Password",
Description = null,
Editor = RuleActionPropertyEditor.Password,
IsRequired = false
});
expected.Properties.Add(new RuleActionProperty
{
Name = "boolean",
Display = "Boolean",
Description = null,
Editor = RuleActionPropertyEditor.Checkbox,
IsRequired = false
});
expected.Properties.Add(new RuleActionProperty
{
Name = "booleanOptional",
Display = "BooleanOptional",
Description = null,
Editor = RuleActionPropertyEditor.Checkbox,
IsRequired = false
});
expected.Properties.Add(new RuleActionProperty
{
Name = "number",
Display = "Number",
Description = null,
Editor = RuleActionPropertyEditor.Number,
IsRequired = true
});
expected.Properties.Add(new RuleActionProperty
{
Name = "numberOptional",
Display = "NumberOptional",
Description = null,
Editor = RuleActionPropertyEditor.Number,
IsRequired = false
});
RuleActionRegistry.Add<MyRuleAction>();
var currentDefinition = RuleActionRegistry.Actions.Values.First();
currentDefinition.Should().BeEquivalentTo(expected);
}
}
}
Loading…
Cancel
Save