Browse Source

Merge pull request #237 from Squidex/feature-algolia

Algolia action.
pull/238/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
3a6fc0de97
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      Squidex.sln.DotSettings
  2. 66
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/AlgoliaAction.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs
  4. 156
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs
  5. 15
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  6. 137
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  7. 1
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  8. 22
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs
  9. 2
      src/Squidex.Domain.Apps.Events/SquidexEvent.cs
  10. 43
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/AlgoliaActionDto.cs
  11. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs
  12. 1
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs
  13. 6
      src/Squidex/Config/Domain/ReadServices.cs
  14. 1
      src/Squidex/Squidex.csproj
  15. 1
      src/Squidex/app/features/rules/declarations.ts
  16. 2
      src/Squidex/app/features/rules/module.ts
  17. 43
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html
  18. 2
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.scss
  19. 62
      src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts
  20. 6
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  21. 1
      src/Squidex/app/shared/services/rules.service.ts
  22. 7
      src/Squidex/app/theme/_rules.scss
  23. 18
      src/Squidex/app/theme/icomoon/demo.html
  24. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  25. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  26. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  27. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  28. 56
      src/Squidex/app/theme/icomoon/icons/algolia.svg
  29. 343
      src/Squidex/app/theme/icomoon/selection.json
  30. 13
      src/Squidex/app/theme/icomoon/style.css
  31. 246
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  32. 56
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/AlgoliaActionTests.cs

9
Squidex.sln.DotSettings

@ -36,12 +36,15 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Header/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Header"&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD;
$FILENAME$&#xD;
Squidex Headless CMS&#xD;
==========================================================================&#xD;
Copyright (c) Squidex Group&#xD;
All rights reserved.&#xD;
Copyright (c) Squidex UG (haftungsbeschraenkt)&#xD;
All rights reserved. Licensed under the MIT license.&#xD;
==========================================================================&#xD;
</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

66
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/AlgoliaAction.cs

@ -0,0 +1,66 @@
// ==========================================================================
// 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(AlgoliaAction))]
public sealed class AlgoliaAction : RuleAction
{
private string appId;
private string apiKey;
private string indexName;
public string AppId
{
get
{
return appId;
}
set
{
ThrowIfFrozen();
appId = value;
}
}
public string ApiKey
{
get
{
return apiKey;
}
set
{
ThrowIfFrozen();
apiKey = value;
}
}
public string IndexName
{
get
{
return indexName;
}
set
{
ThrowIfFrozen();
indexName = value;
}
}
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

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

@ -11,6 +11,8 @@ namespace Squidex.Domain.Apps.Core.Rules
{
public interface IRuleActionVisitor<out T>
{
T Visit(AlgoliaAction action);
T Visit(WebhookAction action);
}
}

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

@ -0,0 +1,156 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Algolia.Search;
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.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction>
{
private const string SchemaNamePlaceholder = "$SCHEMA_NAME";
private readonly ConcurrentDictionary<(string AppId, string ApiKey, string IndexName), Index> clients = new ConcurrentDictionary<(string AppId, string ApiKey, string IndexName), Index>();
private readonly RuleEventFormatter formatter;
public AlgoliaActionHandler(RuleEventFormatter formatter)
{
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
}
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, AlgoliaAction action)
{
var ruleDescription = string.Empty;
var ruleData = new RuleJobData
{
["AppId"] = action.AppId,
["ApiKey"] = action.ApiKey
};
if (@event.Payload is ContentEvent contentEvent)
{
ruleData["ContentId"] = contentEvent.ContentId.ToString();
ruleData["Operation"] = "Upsert";
ruleData["IndexName"] = formatter.FormatString(action.IndexName, @event);
var timestamp = @event.Headers.Timestamp().ToString();
switch (@event.Payload)
{
case ContentCreated created:
{
/*
* Do not add the status property here. Sometimes the published event is faster and therefore
* a content item would never become published. We have to find a way to improve the scheduling
* of rules first before we can fix it.
*/
ruleDescription = $"Add entry to Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject(
new JProperty("id", contentEvent.ContentId),
new JProperty("created", timestamp),
new JProperty("createdBy", created.Actor.ToString()),
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", created.Actor.ToString()),
new JProperty("data", formatter.ToRouteData(created.Data)));
break;
}
case ContentUpdated updated:
{
ruleDescription = $"Update entry in Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject(
new JProperty("lastModified", timestamp),
new JProperty("lastModifiedBy", updated.Actor.ToString()),
new JProperty("data", formatter.ToRouteData(updated.Data)));
break;
}
case ContentStatusChanged statusChanged:
{
ruleDescription = $"Update entry in Algolia index: {action.IndexName}";
ruleData["Content"] = new JObject(
new JProperty("status", statusChanged.Status.ToString()));
break;
}
case ContentDeleted deleted:
{
ruleDescription = $"Delete entry from Index: {action.IndexName}";
ruleData["Content"] = new JObject();
break;
}
}
}
return (ruleDescription, ruleData);
}
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job)
{
var appId = job["AppId"].Value<string>();
var apiKey = job["ApiKey"].Value<string>();
var indexName = job["IndexName"].Value<string>();
var index = clients.GetOrAdd((appId, apiKey, indexName), s =>
{
var client = new AlgoliaClient(appId, apiKey);
return client.InitIndex(indexName);
});
var operation = job["Operation"].Value<string>();
var content = job["Content"].Value<JObject>();
var contentId = job["ContentId"].Value<string>();
try
{
switch (operation)
{
case "Upsert":
{
content["objectID"] = contentId;
var resonse = await index.PartialUpdateObjectAsync(content);
return (resonse.ToString(Formatting.Indented), null);
}
case "Delete":
{
var resonse = await index.DeleteObjectAsync(contentId);
return (resonse.ToString(Formatting.Indented), null);
}
default:
{
return ("Nothing to do!", null);
}
}
}
catch (AlgoliaException ex)
{
return (ex.Message, ex);
}
catch (Exception ex)
{
return (null, ex);
}
}
}
}

15
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs

@ -24,14 +24,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction>
{
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private readonly RuleEventFormatter formatter;
private readonly JsonSerializer serializer;
public WebhookActionHandler(JsonSerializer serializer)
public WebhookActionHandler(RuleEventFormatter formatter)
{
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(formatter, nameof(formatter));
this.serializer = serializer;
this.formatter = formatter;
}
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, WebhookAction action)
@ -55,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
return new JObject(
new JProperty("type", eventName),
new JProperty("payload", JObject.FromObject(@event.Payload, serializer)),
new JProperty("payload", formatter.ToRouteData(@event.Payload)),
new JProperty("timestamp", @event.Headers.Timestamp().ToString()));
}
@ -97,8 +96,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string requestBody)
{
var requestUrl = job["RequestUrl"].ToString();
var requestSignature = job["RequestSignature"].ToString();
var requestUrl = job["RequestUrl"].Value<string>();
var requestSignature = job["RequestSignature"].Value<string>();
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)
{

137
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -0,0 +1,137 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// =========================================-=================================
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules
{
public class RuleEventFormatter
{
private const string Undefined = "UNDEFINED";
private const string AppIdPlaceholder = "$APP_ID";
private const string AppNamePlaceholder = "$APP_NAME";
private const string SchemaIdPlaceholder = "$SCHEMA_ID";
private const string SchemaNamePlaceholder = "$SCHEMA_NAME";
private const string TimestampDatePlaceholder = "$TIMESTAMP_DATE";
private const string TimestampDateTimePlaceholder = "$TIMESTAMP_DATETIME";
private static readonly Regex ContentDataPlaceholder = new Regex(@"\$CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled);
private readonly JsonSerializer serializer;
public RuleEventFormatter(JsonSerializer serializer)
{
Guard.NotNull(serializer, nameof(serializer));
this.serializer = serializer;
}
public virtual JToken ToRouteData(object value)
{
return JToken.FromObject(value, serializer);
}
public virtual string FormatString(string text, Envelope<AppEvent> @event)
{
var sb = new StringBuilder(text);
if (@event.Headers.Contains(CommonHeaders.Timestamp))
{
var timestamp = @event.Headers.Timestamp().ToDateTimeUtc();
sb.Replace(TimestampDateTimePlaceholder, timestamp.ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture));
sb.Replace(TimestampDatePlaceholder, timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture));
}
if (@event.Payload.AppId != null)
{
sb.Replace(AppIdPlaceholder, @event.Payload.AppId.Id.ToString());
sb.Replace(AppNamePlaceholder, @event.Payload.AppId.Name);
}
if (@event.Payload is SchemaEvent schemaEvent && schemaEvent.SchemaId != null)
{
sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString());
sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name);
}
var result = sb.ToString();
if (@event.Payload is ContentCreated contentCreated && contentCreated.Data != null)
{
result = ReplaceData(contentCreated.Data, result);
}
if (@event.Payload is ContentUpdated contentUpdated && contentUpdated.Data != null)
{
result = ReplaceData(contentUpdated.Data, result);
}
return result;
}
private static string ReplaceData(NamedContentData data, string text)
{
return ContentDataPlaceholder.Replace(text, match =>
{
var captures = match.Groups[2].Captures;
var path = new string[captures.Count];
for (var i = 0; i < path.Length; i++)
{
path[i] = captures[i].Value;
}
if (!data.TryGetValue(path[0], out var field))
{
return Undefined;
}
if (!field.TryGetValue(path[1], out var value))
{
return Undefined;
}
for (var j = 2; j < path.Length; j++)
{
if (value is JObject obj && obj.TryGetValue(path[j], out value))
{
continue;
}
if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count)
{
value = arr[idx];
}
else
{
return Undefined;
}
}
if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined)
{
return Undefined;
}
if (value is JValue jValue && jValue != null)
{
return jValue.Value.ToString();
}
return value?.ToString(Formatting.Indented) ?? Undefined;
});
}
}
}

1
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -13,6 +13,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="4.2.1" />
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />

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

@ -24,6 +24,28 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
return action.Accept(visitor);
}
public Task<IEnumerable<ValidationError>> Visit(AlgoliaAction 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.AppId))
{
errors.Add(new ValidationError("Application ID key must be defined.", nameof(action.AppId)));
}
if (string.IsNullOrWhiteSpace(action.IndexName))
{
errors.Add(new ValidationError("Index name key must be defined.", nameof(action.ApiKey)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(WebhookAction action)
{
var errors = new List<ValidationError>();

2
src/Squidex.Domain.Apps.Events/SquidexEvent.cs

@ -12,8 +12,6 @@ namespace Squidex.Domain.Apps.Events
{
public abstract class SquidexEvent : IEvent
{
public string Username { get; set; }
public RefToken Actor { get; set; }
}
}

43
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/AlgoliaActionDto.cs

@ -0,0 +1,43 @@
// ==========================================================================
// 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("Algolia")]
public sealed class AlgoliaActionDto : RuleActionDto
{
/// <summary>
/// The application ID.
/// </summary>
[Required]
public string AppId { get; set; }
/// <summary>
/// The API key to grant access to Squidex.
/// </summary>
[Required]
public string ApiKey { get; set; }
/// <summary>
/// The name of the index.
/// </summary>
[Required]
public string IndexName { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new AlgoliaAction());
}
}
}

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

@ -25,6 +25,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters
return properties.Accept(Instance);
}
public RuleActionDto Visit(AlgoliaAction action)
{
return SimpleMapper.Map(action, new AlgoliaActionDto());
}
public RuleActionDto Visit(WebhookAction action)
{
return SimpleMapper.Map(action, new WebhookActionDto());

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

@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{
[JsonConverter(typeof(JsonInheritanceConverter), "actionType")]
[KnownType(typeof(AlgoliaActionDto))]
[KnownType(typeof(WebhookActionDto))]
public abstract class RuleActionDto
{

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

@ -89,9 +89,15 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppProvider>()
.As<IAppProvider>();
services.AddSingletonAs<RuleEventFormatter>()
.AsSelf();
services.AddSingletonAs<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<AlgoliaActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<WebhookActionHandler>()
.As<IRuleActionHandler>();

1
src/Squidex/Squidex.csproj

@ -48,6 +48,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="4.2.1" />
<PackageReference Include="Ben.BlockingDetector" Version="0.0.2" />
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.0.2-rc" />
<PackageReference Include="IdentityServer4" Version="2.1.1" />

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

@ -5,6 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './pages/rules/actions/algolia-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';

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

@ -14,6 +14,7 @@ import {
} from 'shared';
import {
AlgoliaActionComponent,
ContentChangedTriggerComponent,
RuleEventsPageComponent,
RulesPageComponent,
@ -41,6 +42,7 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
AlgoliaActionComponent,
ContentChangedTriggerComponent,
RuleEventsPageComponent,
RulesPageComponent,

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

@ -0,0 +1,43 @@
<form [formGroup]="actionForm" class="form-horizontal" (ngSubmit)="save()">
<div class="form-group row">
<label class="col col-2 col-form-label" for="appId">App ID</label>
<div class="col col-10">
<sqx-control-errors for="text" [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 col-2 col-form-label" for="apiKey">Api Key</label>
<div class="col col-10">
<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 col-2 col-form-label" for="sharedSecret">Index Name</label>
<div class="col col-10">
<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. Use $SCHEMA_NAME as a placeholder.
</small>
</div>
</div>
</form>

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

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

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

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

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

@ -62,6 +62,12 @@
<div *ngIf="step === 4" class="modal-form">
<div [ngSwitch]="actionType">
<div *ngSwitchCase="'Algolia'">
<sqx-algolia-action #actionControl
[action]="action"
(actionChanged)="selectAction($event)">
</sqx-algolia-action>
</div>
<div *ngSwitchCase="'Webhook'">
<sqx-webhook-action #actionControl
[action]="action"

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

@ -25,6 +25,7 @@ export const ruleTriggers: any = {
};
export const ruleActions: any = {
'Algolia': 'Populate Algolia Index',
'Webhook': 'Send Webhook'
};

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

@ -7,6 +7,9 @@ $trigger-content-changed-icon: #297ff6;
$action-webhook-text: #4bb958;
$action-webhook-icon: #47b353;
$action-algolia-text: #0d9bf9;
$action-algolia-icon: #1690f5;
@mixin build-element($text-color, $icon-color) {
& {
background: $text-color;
@ -54,3 +57,7 @@ $action-webhook-icon: #47b353;
.rule-element-Webhook {
@include build-element($action-webhook-text, $action-webhook-icon)
}
.rule-element-Algolia {
@include build-element($action-algolia-text, $action-algolia-icon)
}

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

@ -9,10 +9,26 @@
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;79)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;80)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: Unknown</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-action-Algolia">
</span>
<span class="mls"> icon-action-Algolia</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e94c" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe94c;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-type-Tags">

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

Binary file not shown.

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

@ -83,6 +83,7 @@
<glyph unicode="&#xe949;" glyph-name="document-lock" d="M358.4 848.457c-28.314 0-51.2-22.886-51.2-51.2v-204.8h51.2v204.8h307.2v-153.6c0-28.314 22.886-51.2 51.2-51.2h153.6v-512h-307.2v-51.2h307.2c28.314 0 51.2 22.886 51.2 51.2v548.2l-219.8 219.8h-292.2zM716.8 761.057l117.4-117.4h-117.4zM153.6 310.857v-281.6h358.4v281.6zM179.2 310.857v76.8c0 84.48 69.12 153.6 153.6 153.6s153.6-69.12 153.6-153.6v-76.8h-51.2v76.8c0 56.32-46.080 102.4-102.4 102.4s-102.4-46.080-102.4-102.4v-76.8z" />
<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="&#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: 68 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.

56
src/Squidex/app/theme/icomoon/icons/algolia.svg

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 95 95"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="algolia-mark-white.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview8"
showgrid="false"
inkscape:zoom="4.9684211"
inkscape:cx="34.823756"
inkscape:cy="51.101934"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<title
id="title4">algolia-new-logo copy</title>
<path
d="m 47.5,2.5 c -24.838038,0 -45,20.13728 -45,45 0,24.86272 20.138266,45 45,45 24.861734,0 45,-20.160975 45,-45.023695 C 92.5,22.613585 72.361734,2.5 47.5,2.5 Z m 0,76.681774 c -17.510724,0 -31.704849,-14.193246 -31.704849,-31.705469 0,-17.510531 14.194125,-31.703773 31.704849,-31.703773 17.512416,0 31.704858,14.193242 31.704858,31.703773 0,17.512223 -14.192442,31.705469 -31.704858,31.705469 z m 0,-56.86776 0,23.618737 c 0,0.690538 0.738152,1.150895 1.359477,0.829322 L 69.805303,35.886114 c 0.467757,-0.24665 0.634977,-0.833681 0.36738,-1.28968 C 65.818286,26.970061 57.75451,21.740258 48.444694,21.39499"
id="path6"
style="fill:#000000;fill-rule:evenodd"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssssssscscccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

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

@ -1,6 +1,35 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M512 26.947c-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 853.496c-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 240.522v254.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"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 0,
"tags": [
"algolia-mark-white"
]
},
"attrs": [
{}
],
"properties": {
"order": 101,
"id": 1,
"name": "action-Algolia",
"prevSize": 32,
"code": 59724
},
"setIdx": 0,
"setId": 2,
"iconIdx": 0
},
{
"icon": {
"paths": [
@ -52,7 +81,7 @@
"code": 59722
},
"setIdx": 1,
"setId": 2,
"setId": 1,
"iconIdx": 0
},
{
@ -80,9 +109,9 @@
"prevSize": 32,
"code": 59652
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 22
"iconIdx": 1
},
{
"icon": {
@ -109,9 +138,9 @@
"prevSize": 32,
"code": 59653
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 23
"iconIdx": 2
},
{
"icon": {
@ -138,9 +167,9 @@
"prevSize": 32,
"code": 59654
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 24
"iconIdx": 3
},
{
"icon": {
@ -167,9 +196,9 @@
"prevSize": 32,
"code": 59655
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 25
"iconIdx": 4
},
{
"icon": {
@ -196,9 +225,9 @@
"prevSize": 32,
"code": 59656
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 26
"iconIdx": 5
},
{
"icon": {
@ -225,9 +254,9 @@
"prevSize": 32,
"code": 59657
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 27
"iconIdx": 6
},
{
"icon": {
@ -254,9 +283,9 @@
"prevSize": 32,
"code": 59658
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 28
"iconIdx": 7
},
{
"icon": {
@ -283,9 +312,9 @@
"prevSize": 32,
"code": 59659
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 29
"iconIdx": 8
},
{
"icon": {
@ -312,9 +341,9 @@
"prevSize": 32,
"code": 59660
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 30
"iconIdx": 9
},
{
"icon": {
@ -341,9 +370,9 @@
"prevSize": 32,
"code": 59661
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 31
"iconIdx": 10
},
{
"icon": {
@ -370,9 +399,9 @@
"prevSize": 32,
"code": 59662
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 32
"iconIdx": 11
},
{
"icon": {
@ -399,9 +428,9 @@
"prevSize": 32,
"code": 59663
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 33
"iconIdx": 12
},
{
"icon": {
@ -428,9 +457,9 @@
"prevSize": 32,
"code": 59664
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 34
"iconIdx": 13
},
{
"icon": {
@ -463,9 +492,9 @@
"prevSize": 32,
"code": 59665
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 35
"iconIdx": 14
},
{
"icon": {
@ -492,9 +521,9 @@
"prevSize": 32,
"code": 59666
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 36
"iconIdx": 15
},
{
"icon": {
@ -521,9 +550,9 @@
"prevSize": 32,
"code": 59667
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 37
"iconIdx": 16
},
{
"icon": {
@ -550,9 +579,9 @@
"prevSize": 32,
"code": 59668
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 38
"iconIdx": 17
},
{
"icon": {
@ -579,9 +608,9 @@
"prevSize": 32,
"code": 59669
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 39
"iconIdx": 18
},
{
"icon": {
@ -608,9 +637,9 @@
"prevSize": 32,
"code": 59670
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 40
"iconIdx": 19
},
{
"icon": {
@ -637,9 +666,9 @@
"prevSize": 32,
"code": 59671
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 41
"iconIdx": 20
},
{
"icon": {
@ -666,9 +695,9 @@
"prevSize": 32,
"code": 59672
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 42
"iconIdx": 21
},
{
"icon": {
@ -696,9 +725,9 @@
"prevSize": 32,
"code": 59713
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 43
"iconIdx": 22
},
{
"icon": {
@ -725,9 +754,9 @@
"prevSize": 32,
"code": 59673
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 44
"iconIdx": 23
},
{
"icon": {
@ -754,9 +783,9 @@
"prevSize": 32,
"code": 59675
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 45
"iconIdx": 24
},
{
"icon": {
@ -783,9 +812,9 @@
"prevSize": 32,
"code": 59676
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 46
"iconIdx": 25
},
{
"icon": {
@ -812,9 +841,9 @@
"prevSize": 32,
"code": 59677
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 47
"iconIdx": 26
},
{
"icon": {
@ -841,9 +870,9 @@
"prevSize": 32,
"code": 59678
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 48
"iconIdx": 27
},
{
"icon": {
@ -870,9 +899,9 @@
"prevSize": 32,
"code": 59679
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 49
"iconIdx": 28
},
{
"icon": {
@ -899,9 +928,9 @@
"prevSize": 32,
"code": 59680
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 50
"iconIdx": 29
},
{
"icon": {
@ -937,9 +966,9 @@
"prevSize": 32,
"code": 59681
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 51
"iconIdx": 30
},
{
"icon": {
@ -966,9 +995,9 @@
"prevSize": 32,
"code": 59682
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 52
"iconIdx": 31
},
{
"icon": {
@ -995,9 +1024,9 @@
"prevSize": 32,
"code": 59683
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 53
"iconIdx": 32
},
{
"icon": {
@ -1024,9 +1053,9 @@
"prevSize": 32,
"code": 59684
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 54
"iconIdx": 33
},
{
"icon": {
@ -1053,9 +1082,9 @@
"prevSize": 32,
"code": 59685
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 55
"iconIdx": 34
},
{
"icon": {
@ -1082,9 +1111,9 @@
"prevSize": 32,
"code": 59674
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 56
"iconIdx": 35
},
{
"icon": {
@ -1111,9 +1140,9 @@
"prevSize": 32,
"code": 59686
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 57
"iconIdx": 36
},
{
"icon": {
@ -1140,9 +1169,9 @@
"prevSize": 32,
"code": 59687
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 58
"iconIdx": 37
},
{
"icon": {
@ -1169,9 +1198,9 @@
"prevSize": 32,
"code": 59688
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 59
"iconIdx": 38
},
{
"icon": {
@ -1198,9 +1227,9 @@
"prevSize": 32,
"code": 59723
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 0
"iconIdx": 39
},
{
"icon": {
@ -1230,9 +1259,9 @@
"prevSize": 32,
"code": 59721
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 1
"iconIdx": 40
},
{
"icon": {
@ -1262,9 +1291,9 @@
"prevSize": 32,
"code": 59711
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 2
"iconIdx": 41
},
{
"icon": {
@ -1292,9 +1321,9 @@
"code": 59648,
"name": "angle-down"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 3
"iconIdx": 42
},
{
"icon": {
@ -1322,9 +1351,9 @@
"code": 59649,
"name": "angle-left"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 4
"iconIdx": 43
},
{
"icon": {
@ -1352,9 +1381,9 @@
"code": 59697,
"name": "angle-right"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 5
"iconIdx": 44
},
{
"icon": {
@ -1382,9 +1411,9 @@
"code": 59651,
"name": "angle-up"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 6
"iconIdx": 45
},
{
"icon": {
@ -1423,9 +1452,9 @@
"prevSize": 32,
"code": 59717
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 7
"iconIdx": 46
},
{
"icon": {
@ -1458,9 +1487,9 @@
"prevSize": 32,
"code": 59720
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 8
"iconIdx": 47
},
{
"icon": {
@ -1488,9 +1517,9 @@
"code": 59709,
"name": "bug"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 9
"iconIdx": 48
},
{
"icon": {
@ -1518,9 +1547,9 @@
"code": 59692,
"name": "caret-down"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 10
"iconIdx": 49
},
{
"icon": {
@ -1548,9 +1577,9 @@
"code": 59690,
"name": "caret-left"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 11
"iconIdx": 50
},
{
"icon": {
@ -1578,9 +1607,9 @@
"code": 59689,
"name": "caret-right"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 12
"iconIdx": 51
},
{
"icon": {
@ -1608,9 +1637,9 @@
"code": 59691,
"name": "caret-up"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 13
"iconIdx": 52
},
{
"icon": {
@ -1646,9 +1675,9 @@
"prevSize": 32,
"code": 59718
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 14
"iconIdx": 53
},
{
"icon": {
@ -1675,9 +1704,9 @@
"prevSize": 32,
"code": 59702
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 15
"iconIdx": 54
},
{
"icon": {
@ -1704,9 +1733,9 @@
"prevSize": 32,
"code": 59703
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 16
"iconIdx": 55
},
{
"icon": {
@ -1733,9 +1762,9 @@
"prevSize": 32,
"code": 59704
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 17
"iconIdx": 56
},
{
"icon": {
@ -1760,9 +1789,9 @@
"prevSize": 32,
"code": 61450
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 18
"iconIdx": 57
},
{
"icon": {
@ -1789,9 +1818,9 @@
"prevSize": 32,
"code": 61641
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 19
"iconIdx": 58
},
{
"icon": {
@ -1818,9 +1847,9 @@
"prevSize": 32,
"code": 59698
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 20
"iconIdx": 59
},
{
"icon": {
@ -1868,9 +1897,9 @@
"prevSize": 32,
"code": 59719
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 21
"iconIdx": 60
},
{
"icon": {
@ -1905,9 +1934,9 @@
"prevSize": 32,
"code": 59650
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 60
"iconIdx": 61
},
{
"icon": {
@ -1938,9 +1967,9 @@
"prevSize": 32,
"code": 59850
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 61
"iconIdx": 62
},
{
"icon": {
@ -1971,9 +2000,9 @@
"code": 59715,
"name": "elapsed"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 62
"iconIdx": 63
},
{
"icon": {
@ -2001,9 +2030,9 @@
"code": 59707,
"name": "google"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 63
"iconIdx": 64
},
{
"icon": {
@ -2033,9 +2062,9 @@
"code": 59700,
"name": "lock"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 64
"iconIdx": 65
},
{
"icon": {
@ -2064,9 +2093,9 @@
"code": 59712,
"name": "microsoft"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 65
"iconIdx": 66
},
{
"icon": {
@ -2094,9 +2123,9 @@
"code": 59695,
"name": "pause"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 66
"iconIdx": 67
},
{
"icon": {
@ -2124,9 +2153,9 @@
"code": 59696,
"name": "play"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 67
"iconIdx": 68
},
{
"icon": {
@ -2163,9 +2192,9 @@
"code": 59694,
"name": "reset"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 68
"iconIdx": 69
},
{
"icon": {
@ -2198,9 +2227,9 @@
"code": 59693,
"name": "settings2"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 69
"iconIdx": 70
},
{
"icon": {
@ -2232,9 +2261,9 @@
"code": 59716,
"name": "timeout"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 70
"iconIdx": 71
},
{
"icon": {
@ -2262,9 +2291,9 @@
"code": 59699,
"name": "unlocked"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 71
"iconIdx": 72
},
{
"icon": {
@ -2295,9 +2324,9 @@
"code": 59701,
"name": "browser"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 72
"iconIdx": 73
},
{
"icon": {
@ -2327,9 +2356,9 @@
"code": 59714,
"name": "checkmark"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 73
"iconIdx": 74
},
{
"icon": {
@ -2357,9 +2386,9 @@
"code": 59706,
"name": "control-Stars"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 74
"iconIdx": 75
},
{
"icon": {
@ -2386,9 +2415,9 @@
"code": 59705,
"name": "control-RichText"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 75
"iconIdx": 76
},
{
"icon": {
@ -2415,9 +2444,9 @@
"code": 59710,
"name": "download"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 76
"iconIdx": 77
},
{
"icon": {
@ -2444,9 +2473,9 @@
"code": 59708,
"name": "info"
},
"setIdx": 2,
"setIdx": 1,
"setId": 1,
"iconIdx": 77
"iconIdx": 78
}
],
"height": 1024,

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

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?pcikfl');
src: url('fonts/icomoon.eot?pcikfl#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?pcikfl') format('truetype'),
url('fonts/icomoon.woff?pcikfl') format('woff'),
url('fonts/icomoon.svg?pcikfl#icomoon') format('svg');
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');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,9 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-action-Algolia:before {
content: "\e94c";
}
.icon-type-Tags:before {
content: "\e94a";
color: #c1c6c8;

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

@ -0,0 +1,246 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
public class RuleEventFormatterTests
{
private readonly JsonSerializer serializer = JsonSerializer.CreateDefault();
private readonly RuleEventFormatter sut;
public RuleEventFormatterTests()
{
sut = new RuleEventFormatter(serializer);
}
[Fact]
public void Should_serialize_object_to_json()
{
var result = sut.ToRouteData(new { Value = 1 });
Assert.True(result is JObject);
}
[Fact]
public void Should_replace_app_information_from_event()
{
var appId = Guid.NewGuid();
var @event = new ContentCreated
{
AppId = new NamedId<Guid>(appId, "my-app")
};
var result = sut.FormatString("Name $APP_NAME has id $APP_ID", Envelope.Create(@event).To<AppEvent>());
Assert.Equal($"Name my-app has id {appId}", result);
}
[Fact]
public void Should_replace_schema_information_from_event()
{
var schemaId = Guid.NewGuid();
var @event = new ContentCreated
{
SchemaId = new NamedId<Guid>(schemaId, "my-schema")
};
var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", Envelope.Create(@event).To<AppEvent>());
Assert.Equal($"Name my-schema has id {schemaId}", result);
}
[Fact]
public void Should_replace_timestamp_information_from_event()
{
var now = DateTime.UtcNow;
var envelope = Envelope.Create(new ContentCreated()).To<AppEvent>().SetTimestamp(Instant.FromDateTimeUtc(now));
var result = sut.FormatString("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope);
Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result);
}
[Fact]
public void Should_return_undefined_when_field_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.country.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_partition_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.city.de", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_array_item_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JArray()))
};
var result = sut.FormatString("$CONTENT_DATA.city.de.10", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_property_not_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JObject(
new JProperty("name", "Berlin"))))
};
var result = sut.FormatString("$CONTENT_DATA.city.de.Name", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_plain_value_when_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", "Berlin"))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_undefined_when_null()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", JValue.CreateNull()))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_undefined_when_undefined()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", JValue.CreateUndefined()))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("UNDEFINED", result);
}
[Fact]
public void Should_return_string_when_object()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JObject(
new JProperty("name", "Berlin"))))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>());
Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result);
}
[Fact]
public void Should_return_plain_value_from_array_when_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JArray(
"Berlin")))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv.0", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("Berlin", result);
}
[Fact]
public void Should_return_plain_value_from_object_when_found()
{
var @event = new ContentCreated
{
Data =
new NamedContentData()
.AddField("city", new ContentFieldData()
.AddValue("iv", new JObject(
new JProperty("name", "Berlin"))))
};
var result = sut.FormatString("$CONTENT_DATA.city.iv.name", Envelope.Create(@event).To<AppEvent>());
Assert.Equal("Berlin", result);
}
}
}

56
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/AlgoliaActionTests.cs

@ -0,0 +1,56 @@
// ==========================================================================
// 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 AlgoliaActionTests
{
[Fact]
public async Task Should_add_error_if_app_id_not_defined()
{
var action = new AlgoliaAction { AppId = null, ApiKey = "KEY", IndexName = "IDX" };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.NotEmpty(errors);
}
[Fact]
public async Task Should_add_error_if_api_key_not_defined()
{
var action = new AlgoliaAction { AppId = "APP", ApiKey = null, IndexName = "IDX" };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.NotEmpty(errors);
}
[Fact]
public async Task Should_add_error_if_index_name_not_defined()
{
var action = new AlgoliaAction { AppId = "APP", ApiKey = "KEY", IndexName = null };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.NotEmpty(errors);
}
[Fact]
public async Task Should_not_add_error_everything_defined()
{
var action = new AlgoliaAction { AppId = "APP", ApiKey = "KEY", IndexName = "IDX" };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.Empty(errors);
}
}
}
Loading…
Cancel
Save