Browse Source

Fastly action.

pull/241/head
Sebastian Stehle 8 years ago
parent
commit
96d1dad46e
  1. 24
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/FastlyAction.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs
  3. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs
  4. 92
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs
  5. 19
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs
  6. 36
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/FastlyActionDto.cs
  7. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs
  8. 1
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs
  9. 3
      src/Squidex/Config/Domain/ReadServices.cs
  10. 1
      src/Squidex/app/features/rules/declarations.ts
  11. 2
      src/Squidex/app/features/rules/module.ts
  12. 2
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html
  13. 4
      src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.html
  14. 31
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.html
  15. 2
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.scss
  16. 58
      src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts
  17. 2
      src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html
  18. 2
      src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html
  19. 6
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  20. 2
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.scss
  21. 2
      src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html
  22. 2
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html
  23. 1
      src/Squidex/app/shared/services/rules.service.ts
  24. 9
      src/Squidex/app/theme/_rules.scss
  25. 46
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/FastlyActionTests.cs

24
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/FastlyAction.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
{
[TypeName(nameof(FastlyAction))]
public sealed class FastlyAction : RuleAction
{
public string ApiKey { get; set; }
public string ServiceId { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

2
src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Rules
T Visit(AzureQueueAction action);
T Visit(FastlyAction action);
T Visit(SlackAction action);
T Visit(WebhookAction action);

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs

@ -108,7 +108,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job)
{
if (job["Operation"] == null)
if (!job.TryGetValue("Operation", out var operationToken))
{
return (null, new InvalidOperationException("The action cannot handle this event."));
}
@ -119,7 +119,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
var index = clients.GetClient((appId, apiKey, indexName));
var operation = job["Operation"].Value<string>();
var operation = operationToken.Value<string>();
var content = job["Content"].Value<JObject>();
var contentId = job["ContentId"].Value<string>();

92
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs

@ -0,0 +1,92 @@
// ==========================================================================
// 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 FastlyActionHandler : RuleActionHandler<FastlyAction>
{
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, FastlyAction action)
{
var ruleDescription = "Purge key in fastly";
var ruleData = new RuleJobData
{
["FastlyApiKey"] = action.ApiKey,
["FastlyServiceID"] = action.ServiceId
};
if (@event.Headers.Contains(CommonHeaders.AggregateId))
{
ruleData["Key"] = @event.Headers.AggregateId().ToString();
}
return (ruleDescription, ruleData);
}
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job)
{
if (!job.TryGetValue("Key", out var keyToken))
{
return (null, new InvalidOperationException("The action cannot handle this event."));
}
var requestMsg = BuildRequest(job, keyToken.Value<string>());
HttpResponseMessage response = null;
try
{
response = await HttpClientPool.GetHttpClient().SendAsync(requestMsg);
var responseString = await response.Content.ReadAsStringAsync();
var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, responseString, TimeSpan.Zero, false);
return (requestDump, null);
}
catch (Exception ex)
{
if (requestMsg != null)
{
var requestDump = DumpFormatter.BuildDump(requestMsg, response, null, 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 key)
{
var serviceId = job["FastlyServiceID"].Value<string>();
var requestUrl = $"https://api.fastly.com/service/{serviceId}/purge/{key}";
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Headers.Add("Fastly-Key", job["FastlyApiKey"].Value<string>());
return request;
}
}
}

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

@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (string.IsNullOrWhiteSpace(action.IndexName))
{
errors.Add(new ValidationError("Index name key must be defined.", nameof(action.ApiKey)));
errors.Add(new ValidationError("Index name must be defined.", nameof(action.ApiKey)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
@ -68,6 +68,23 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(FastlyAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.ApiKey))
{
errors.Add(new ValidationError("Api key must be defined.", nameof(action.ApiKey)));
}
if (string.IsNullOrWhiteSpace(action.ServiceId))
{
errors.Add(new ValidationError("Service name must be defined.", nameof(action.ServiceId)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(SlackAction action)
{
var errors = new List<ValidationError>();

36
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/FastlyActionDto.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
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("Fastly")]
public sealed class FastlyActionDto : RuleActionDto
{
/// <summary>
/// The ID of the fastly service.
/// </summary>
[Required]
public string ServiceId { get; set; }
/// <summary>
/// The API key to grant access to Squidex.
/// </summary>
[Required]
public string ApiKey { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new FastlyAction());
}
}
}

5
src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs

@ -35,6 +35,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters
return SimpleMapper.Map(action, new AzureQueueActionDto());
}
public RuleActionDto Visit(FastlyAction action)
{
return SimpleMapper.Map(action, new FastlyActionDto());
}
public RuleActionDto Visit(SlackAction action)
{
return SimpleMapper.Map(action, new SlackActionDto());

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

@ -15,6 +15,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
[JsonConverter(typeof(JsonInheritanceConverter), "actionType")]
[KnownType(typeof(AlgoliaActionDto))]
[KnownType(typeof(AzureQueueActionDto))]
[KnownType(typeof(FastlyActionDto))]
[KnownType(typeof(SlackActionDto))]
[KnownType(typeof(WebhookActionDto))]
public abstract class RuleActionDto

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

@ -104,6 +104,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AzureQueueActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<FastlyActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<SlackActionHandler>()
.As<IRuleActionHandler>();

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

@ -7,6 +7,7 @@
export * from './pages/rules/actions/algolia-action.component';
export * from './pages/rules/actions/azure-queue-action.component';
export * from './pages/rules/actions/fastly-action.component';
export * from './pages/rules/actions/slack-action.component';
export * from './pages/rules/actions/webhook-action.component';
export * from './pages/rules/triggers/asset-changed-trigger.component';

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

@ -19,6 +19,7 @@ import {
AssetChangedTriggerComponent,
AzureQueueActionComponent,
ContentChangedTriggerComponent,
FastlyActionComponent,
RuleEventsPageComponent,
RulesPageComponent,
RuleWizardComponent,
@ -57,6 +58,7 @@ const routes: Routes = [
AssetChangedTriggerComponent,
AzureQueueActionComponent,
ContentChangedTriggerComponent,
FastlyActionComponent,
RuleEventsPageComponent,
RulesPageComponent,
RuleWizardComponent,

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

@ -1,4 +1,4 @@
<h3>Populate index in algolia with content</h3>
<h3 class="rule-title">Populate index in algolia with content</h3>
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">

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

@ -1,8 +1,8 @@
<h3>Send event payload to Azure Storage Queue</h3>
<h3 class="rule-title">Send event payload to Azure Storage Queue</h3>
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">
<label class="col col-3 col-form-label" for="queue">Connection String</label>
<label class="col col-3 col-form-label" for="connectionString">Connection String</label>
<div class="col col-9">
<sqx-control-errors for="text" [submitted]="actionFormSubmitted"></sqx-control-errors>

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

@ -0,0 +1,31 @@
<h3 class="rule-title">Purge cache entries in Fastly</h3>
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">
<label class="col col-3 col-form-label" for="serviceId">Service ID</label>
<div class="col col-9">
<sqx-control-errors for="text" [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 col-3 col-form-label" for="apiKey">Api Key</label>
<div class="col 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>
</form>

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

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

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

@ -0,0 +1,58 @@
/*
* 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-fastly-action',
styleUrls: ['./fastly-action.component.scss'],
templateUrl: './fastly-action.component.html'
})
export class FastlyActionComponent implements OnInit {
@Input()
public action: any;
@Output()
public actionChanged = new EventEmitter<object>();
public actionFormSubmitted = false;
public actionForm =
this.formBuilder.group({
serviceId: ['',
[
Validators.required
]],
apiKey: ['',
[
Validators.required
]]
});
constructor(
private readonly formBuilder: FormBuilder
) {
}
public ngOnInit() {
this.action = Object.assign({}, { serviceId: '', apiKey: '' }, 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/slack-action.component.html

@ -1,4 +1,4 @@
<h3>Send custom text to an incoming webhook in Slack</h3>
<h3 class="rule-title">Send custom text to an incoming webhook in Slack</h3>
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">

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

@ -1,4 +1,4 @@
<h3>Send event payload to webhook</h3>
<h3 class="rule-title">Send event payload to webhook</h3>
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">

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

@ -80,6 +80,12 @@
(actionChanged)="selectAction($event)">
</sqx-azure-queue-action>
</div>
<div *ngSwitchCase="'Fastly'">
<sqx-fastly-action #actionControl
[action]="action"
(actionChanged)="selectAction($event)">
</sqx-fastly-action>
</div>
<div *ngSwitchCase="'Slack'">
<sqx-slack-action #actionControl
[action]="action"

2
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.scss

@ -24,7 +24,7 @@
}
&-form {
padding-top: 2em;
padding-top: 1em;
padding-bottom: 0;
}
}

2
src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html

@ -1,4 +1,4 @@
<h3>Trigger rule when asset has been...</h3>
<h3 class="rule-title">Trigger rule when asset has been...</h3>
<form [formGroup]="triggerForm" class="form-horizontal" (ngSubmit)="save()">

2
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html

@ -1,4 +1,4 @@
<h3>Trigger rule when the events for the following schemas happen</h3>
<h3 class="rule-title">Trigger rule when an events for a schemas happens</h3>
<table class="table table-middle table-sm table-fixed table-borderless">
<colgroup>

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

@ -28,6 +28,7 @@ export const ruleTriggers: any = {
export const ruleActions: any = {
'Algolia': 'Populate Algolia Index',
'AzureQueue': 'Send to Azure Queue',
'Fastly': 'Purge fastly Cache',
'Slack': 'Send to Slack',
'Webhook': 'Send Webhook'
};

9
src/Squidex/app/theme/_rules.scss

@ -8,6 +8,7 @@ $action-webhook: #4bb958;
$action-algolia: #0d9bf9;
$action-slack: #5c3a58;
$action-azure: #55b3ff;
$action-fastly: #e23335;
@mixin build-element($color) {
& {
@ -23,6 +24,10 @@ $action-azure: #55b3ff;
}
}
.rule-title {
margin-bottom: 1rem;
}
.rule-element {
& {
@include truncate;
@ -65,6 +70,10 @@ $action-azure: #55b3ff;
@include build-element($action-algolia);
}
.rule-element-Fastly {
@include build-element($action-fastly);
}
.rule-element-Slack {
@include build-element($action-slack);
}

46
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/FastlyActionTests.cs

@ -0,0 +1,46 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
{
public sealed class FastlyActionTests
{
[Fact]
public async Task Should_add_error_if_service_id_not_defined()
{
var action = new FastlyAction { ServiceId = null, ApiKey = "KEY" };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.NotEmpty(errors);
}
[Fact]
public async Task Should_add_error_if_api_key_not_defined()
{
var action = new FastlyAction { ServiceId = "APP", ApiKey = null };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.NotEmpty(errors);
}
[Fact]
public async Task Should_not_add_error_everything_defined()
{
var action = new FastlyAction { ServiceId = "APP", ApiKey = "KEY" };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.Empty(errors);
}
}
}
Loading…
Cancel
Save