Browse Source

Rule UI updated.

pull/157/head
Sebastian Stehle 9 years ago
parent
commit
6574f2ac5f
  1. 1
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs
  2. 14
      src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs
  3. 14
      src/Squidex.Domain.Apps.Events/SquidexEvents.cs
  4. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs
  5. 14
      src/Squidex.Infrastructure/SquidexInfrastructure.cs
  6. 36
      src/Squidex.Infrastructure/TypeNameRegistry.cs
  7. 10
      src/Squidex/Config/Domain/Serializers.cs
  8. 72
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  9. 24
      src/Squidex/app/features/rules/pages/rules/rules-page.component.scss
  10. 44
      src/Squidex/app/features/rules/pages/rules/rules-page.component.ts
  11. 4
      src/Squidex/app/framework/angular/toggle.component.scss
  12. 2
      src/Squidex/app/framework/angular/toggle.component.ts
  13. 6
      src/Squidex/app/shared/services/rules.service.ts
  14. 2
      tests/Benchmarks/Tests/HandleEvents.cs
  15. 2
      tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs
  16. 2
      tests/Squidex.Infrastructure.Tests/Actors/ActorRemoteTests.cs
  17. 4
      tests/Squidex.Infrastructure.Tests/CQRS/Events/EventDataFormatterTests.cs
  18. 22
      tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs

1
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs

@ -84,7 +84,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
new TagsField(id, name, partitioning, (TagsFieldProperties)properties)); new TagsField(id, name, partitioning, (TagsFieldProperties)properties));
typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "DateTime"); typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "DateTime");
typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "References"); typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "References");
} }

14
src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs

@ -0,0 +1,14 @@
// ==========================================================================
// SquidexCoreModel.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Domain.Apps.Core
{
public static class SquidexCoreModel
{
}
}

14
src/Squidex.Domain.Apps.Events/SquidexEvents.cs

@ -0,0 +1,14 @@
// ==========================================================================
// SquidexEvents.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Domain.Apps.Events
{
public static class SquidexEvents
{
}
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{ {
await EnsureRulesLoadedAsync(); await EnsureRulesLoadedAsync();
return inMemoryRules.GetOrDefault(appId) ?? EmptyRules; return inMemoryRules.GetOrDefault(appId)?.ToList() ?? EmptyRules;
} }
private async Task EnsureRulesLoadedAsync() private async Task EnsureRulesLoadedAsync()

14
src/Squidex.Infrastructure/SquidexInfrastructure.cs

@ -0,0 +1,14 @@
// ==========================================================================
// SquidexInfrastructure.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure
{
public static class SquidexInfrastructure
{
}
}

36
src/Squidex.Infrastructure/TypeNameRegistry.cs

@ -17,20 +17,6 @@ namespace Squidex.Infrastructure
private readonly Dictionary<Type, string> namesByType = new Dictionary<Type, string>(); private readonly Dictionary<Type, string> namesByType = new Dictionary<Type, string>();
private readonly Dictionary<string, Type> typesByName = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Type> typesByName = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public TypeNameRegistry Map(Type type)
{
Guard.NotNull(type, nameof(type));
var typeNameAttribute = type.GetTypeInfo().GetCustomAttribute<TypeNameAttribute>();
if (typeNameAttribute != null)
{
Map(type, typeNameAttribute.TypeName);
}
return this;
}
public TypeNameRegistry MapObsolete(Type type, string name) public TypeNameRegistry MapObsolete(Type type, string name)
{ {
Guard.NotNull(type, nameof(type)); Guard.NotNull(type, nameof(type));
@ -56,6 +42,20 @@ namespace Squidex.Infrastructure
return this; return this;
} }
public TypeNameRegistry Map(Type type)
{
Guard.NotNull(type, nameof(type));
var typeNameAttribute = type.GetTypeInfo().GetCustomAttribute<TypeNameAttribute>();
if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName))
{
Map(type, typeNameAttribute.TypeName);
}
return this;
}
public TypeNameRegistry Map(Type type, string name) public TypeNameRegistry Map(Type type, string name)
{ {
Guard.NotNull(type, nameof(type)); Guard.NotNull(type, nameof(type));
@ -95,15 +95,13 @@ namespace Squidex.Infrastructure
return this; return this;
} }
public TypeNameRegistry Map(Assembly assembly) public TypeNameRegistry MapUnmapped(Assembly assembly)
{ {
foreach (var type in assembly.GetTypes()) foreach (var type in assembly.GetTypes())
{ {
var typeNameAttribute = type.GetTypeInfo().GetCustomAttribute<TypeNameAttribute>(); if (!namesByType.ContainsKey(type))
if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName))
{ {
Map(type, typeNameAttribute.TypeName); Map(type);
} }
} }

10
src/Squidex/Config/Domain/Serializers.cs

@ -6,18 +6,18 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Reflection;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using NodaTime; using NodaTime;
using NodaTime.Serialization.JsonNet; using NodaTime.Serialization.JsonNet;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps.Json; using Squidex.Domain.Apps.Core.Apps.Json;
using Squidex.Domain.Apps.Core.Rules.Json;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Core.Schemas.Json;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
@ -45,6 +45,7 @@ namespace Squidex.Config.Domain
new NamedStringIdConverter(), new NamedStringIdConverter(),
new PropertiesBagConverter(), new PropertiesBagConverter(),
new RefTokenConverter(), new RefTokenConverter(),
new RuleConverter(),
new SchemaConverter(FieldRegistry), new SchemaConverter(FieldRegistry),
new StringEnumConverter()); new StringEnumConverter());
@ -62,8 +63,9 @@ namespace Squidex.Config.Domain
static Serializers() static Serializers()
{ {
TypeNameRegistry.Map(typeof(SquidexEvent).GetTypeInfo().Assembly); TypeNameRegistry.MapUnmapped(typeof(SquidexCoreModel).Assembly);
TypeNameRegistry.Map(typeof(NoopEvent).GetTypeInfo().Assembly); TypeNameRegistry.MapUnmapped(typeof(SquidexEvents).Assembly);
TypeNameRegistry.MapUnmapped(typeof(SquidexInfrastructure).Assembly);
ConfigureJson(SerializerSettings, TypeNameHandling.Auto); ConfigureJson(SerializerSettings, TypeNameHandling.Auto);

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

@ -1,6 +1,6 @@
<sqx-title message="{app} | Rules" parameter1="app" value1="{{appName() | async}}"></sqx-title> <sqx-title message="{app} | Rules" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-panel panelWidth="50rem"> <sqx-panel panelWidth="54rem">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title-row"> <div class="panel-title-row">
<div class="float-right"> <div class="float-right">
@ -29,14 +29,69 @@
<div class="table-items-row table-items-row-empty" *ngIf="rules && rules.length === 0"> <div class="table-items-row table-items-row-empty" *ngIf="rules && rules.length === 0">
No Rule created yet. No Rule created yet.
</div> </div>
<table class="table table-items table-fixed" *ngIf="rules && rules.length > 0">
<colgroup>
<col style="width: 40px" />
<col style="width: 50%" />
<col style="width: 50px" />
<col style="width: 50%" />
<col style="width: 50px" />
<col style="width: 70px" />
</colgroup>
<tbody>
<ng-template ngFor let-rule [ngForOf]="rules">
<tr>
<td class="step-if">
<h3>If</h3>
</td>
<td>
<span class="rule-element rule-element-{{rule.triggerType}}">
<span class="rule-element-icon">
<i class="icon-trigger-{{rule.triggerType}}"></i>
</span>
<span class="rule-element-text">
{{ruleTriggers[rule.triggerType]}}
</span>
</span>
</td>
<td class="step-then">
<h3>then</h3>
</td>
<td>
<span class="rule-element rule-element-{{rule.actionType}}">
<span class="rule-element-icon">
<i class="icon-action-{{rule.actionType}}"></i>
</span>
<span class="rule-element-text">
{{ruleActions[rule.actionType]}}
</span>
</span>
</td>
<td>
<sqx-toggle [ngModel]="rule.isEnabled" (ngModelChange)="toggleRule(rule)"></sqx-toggle>
</td>
<td>
<button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="deleteRule(rule)"
confirmTitle="Delete rule"
confirmText="Do you really want to delete the rule?">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr class="spacer"></tr>
</ng-template>
</tbody>
</table>
</div> </div>
</div>
<div class="panel-sidebar"> <div class="panel-sidebar">
<a class="panel-link" routerLink="events" routerLinkActive="active" #linkHistory> <a class="panel-link" routerLink="events" routerLinkActive="active" #linkHistory>
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
</div>
</div> </div>
</sqx-panel> </sqx-panel>
@ -44,7 +99,8 @@
<div class="modal-backdrop"></div> <div class="modal-backdrop"></div>
<sqx-rule-wizard [schemas]="schemas" <sqx-rule-wizard [schemas]="schemas"
(cancelled)="addRuleDialog.hide()"> (cancelled)="addRuleDialog.hide()"
(created)="onRuleCreated($event)">
</sqx-rule-wizard> </sqx-rule-wizard>
</div> </div>

24
src/Squidex/app/features/rules/pages/rules/rules-page.component.scss

@ -1,10 +1,26 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
.failed { sqx-toggle {
color: $color-theme-error; display: inline-block;
} }
.success { .table-items {
color: $color-theme-green; tbody {
td {
padding-bottom: .5rem;
}
}
}
.step-if {
padding-left: 1.25rem;
padding-right: 0;
text-align: left;
}
.step-then {
padding-left: 0;
padding-right: 0;
text-align: center;
} }

44
src/Squidex/app/features/rules/pages/rules/rules-page.component.ts

@ -11,10 +11,13 @@ import {
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
AuthService, AuthService,
DateTime,
DialogService, DialogService,
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
ModalView, ModalView,
ruleActions,
ruleTriggers,
RuleDto, RuleDto,
RulesService, RulesService,
SchemaDto, SchemaDto,
@ -30,7 +33,10 @@ import {
] ]
}) })
export class RulesPageComponent extends AppComponentBase implements OnInit { export class RulesPageComponent extends AppComponentBase implements OnInit {
public addRuleDialog = new ModalView(true, false); public ruleActions = ruleActions;
public ruleTriggers = ruleTriggers;
public addRuleDialog = new ModalView();
public rules: ImmutableArray<RuleDto>; public rules: ImmutableArray<RuleDto>;
public schemas: SchemaDto[]; public schemas: SchemaDto[];
@ -63,4 +69,40 @@ export class RulesPageComponent extends AppComponentBase implements OnInit {
this.notifyError(error); this.notifyError(error);
}); });
} }
public onRuleCreated(rule: RuleDto) {
this.rules = this.rules.push(rule);
this.addRuleDialog.hide();
}
public toggleRule(rule: RuleDto) {
if (rule.isEnabled) {
this.appNameOnce()
.switchMap(app => this.rulesService.disableRule(app, rule.id, rule.version))
.subscribe(dto => {
this.rules = this.rules.replace(rule, rule.disable(this.authService.user.id, dto.version, DateTime.now()));
}, error => {
this.notifyError(error);
});
} else {
this.appNameOnce()
.switchMap(app => this.rulesService.enableRule(app, rule.id, rule.version))
.subscribe(dto => {
this.rules = this.rules.replace(rule, rule.enable(this.authService.user.id, dto.version, DateTime.now()));
}, error => {
this.notifyError(error);
});
}
}
public deleteRule(rule: RuleDto) {
this.appNameOnce()
.switchMap(app => this.rulesService.deleteRule(app, rule.id, rule.version))
.subscribe(dto => {
this.rules = this.rules.remove(rule);
}, error => {
this.notifyError(error);
});
}
} }

4
src/Squidex/app/framework/angular/toggle.component.scss

@ -1,8 +1,8 @@
@import '_mixins'; @import '_mixins';
@import '_vars'; @import '_vars';
$toggle-width: 3.2rem; $toggle-width: 2.2rem;
$toggle-height: 2rem; $toggle-height: 1.4rem;
$toggle-button-size: $toggle-height - .3rem; $toggle-button-size: $toggle-height - .3rem;
.toggle { .toggle {

2
src/Squidex/app/framework/angular/toggle.component.ts

@ -28,7 +28,7 @@ export class ToggleComponent implements ControlValueAccessor {
public isDisabled = false; public isDisabled = false;
public writeValue(value: boolean | null | undefined) { public writeValue(value: boolean | null | undefined) {
this.isChecked = Types.isBoolean(value) ? value || null : null; this.isChecked = Types.isBoolean(value) ? value : null;
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {

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

@ -25,7 +25,7 @@ export const ruleTriggers: any = {
}; };
export const ruleActions: any = { export const ruleActions: any = {
'Webhook': 'Send Webhooks' 'Webhook': 'Send Webhook'
}; };
export class RuleDto { export class RuleDto {
@ -52,9 +52,9 @@ export class RuleDto {
version, version,
this.isEnabled, this.isEnabled,
update.trigger, update.trigger,
update.trigger['triggerType'], update.trigger.triggerType,
update.action, update.action,
update.action['actionType']); update.action.actionType);
} }
public enable(user: string, version: Version, now?: DateTime): RuleDto { public enable(user: string, version: Version, now?: DateTime): RuleDto {

2
tests/Benchmarks/Tests/HandleEvents.cs

@ -21,7 +21,7 @@ namespace Benchmarks.Tests
public sealed class HandleEvents : IBenchmark public sealed class HandleEvents : IBenchmark
{ {
private const int NumEvents = 5000; private const int NumEvents = 5000;
private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry().Map(typeof(MyEvent)); private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry().MapUnmapped(typeof(MyEvent));
private readonly EventDataFormatter formatter; private readonly EventDataFormatter formatter;
private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
private IMongoClient mongoClient; private IMongoClient mongoClient;

2
tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs

@ -23,7 +23,7 @@ namespace Benchmarks.Tests
{ {
private const int NumCommits = 200; private const int NumCommits = 200;
private const int NumStreams = 10; private const int NumStreams = 10;
private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry().Map(typeof(MyEvent)); private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry().MapUnmapped(typeof(MyEvent));
private readonly EventDataFormatter formatter; private readonly EventDataFormatter formatter;
private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
private IMongoClient mongoClient; private IMongoClient mongoClient;

2
tests/Squidex.Infrastructure.Tests/Actors/ActorRemoteTests.cs

@ -49,7 +49,7 @@ namespace Squidex.Infrastructure.Actors
public ActorRemoteTests() public ActorRemoteTests()
{ {
registry.Map(typeof(SuccessMessage)); registry.MapUnmapped(typeof(SuccessMessage));
actors = new RemoteActors(new DefaultRemoteActorChannel(new InMemoryPubSub(), registry)); actors = new RemoteActors(new DefaultRemoteActorChannel(new InMemoryPubSub(), registry));
actors.Connect("my", actor); actors.Connect("my", actor);

4
tests/Squidex.Infrastructure.Tests/CQRS/Events/EventDataFormatterTests.cs

@ -40,8 +40,8 @@ namespace Squidex.Infrastructure.CQRS.Events
{ {
serializerSettings.Converters.Add(new PropertiesBagConverter()); serializerSettings.Converters.Add(new PropertiesBagConverter());
typeNameRegistry.Map(typeof(MyEvent), "Event"); typeNameRegistry.MapUnmapped(typeof(MyEvent), "Event");
typeNameRegistry.Map(typeof(MyOldEvent), "OldEvent"); typeNameRegistry.MapUnmapped(typeof(MyOldEvent), "OldEvent");
sut = new EventDataFormatter(typeNameRegistry, serializerSettings); sut = new EventDataFormatter(typeNameRegistry, serializerSettings);
} }

22
tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs

@ -30,7 +30,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_register_and_retrieve_types() public void Should_register_and_retrieve_types()
{ {
sut.Map(typeof(int), "NumberField"); sut.MapUnmapped(typeof(int), "NumberField");
Assert.Equal("NumberField", sut.GetName<int>()); Assert.Equal("NumberField", sut.GetName<int>());
Assert.Equal("NumberField", sut.GetName(typeof(int))); Assert.Equal("NumberField", sut.GetName(typeof(int)));
@ -42,7 +42,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_register_from_attribute() public void Should_register_from_attribute()
{ {
sut.Map(typeof(MyType)); sut.MapUnmapped(typeof(MyType));
Assert.Equal("my", sut.GetName<MyType>()); Assert.Equal("my", sut.GetName<MyType>());
Assert.Equal("my", sut.GetName(typeof(MyType))); Assert.Equal("my", sut.GetName(typeof(MyType)));
@ -54,7 +54,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_register_from_assembly() public void Should_register_from_assembly()
{ {
sut.Map(typeof(TypeNameRegistryTests).GetTypeInfo().Assembly); sut.MapUnmapped(typeof(TypeNameRegistryTests).GetTypeInfo().Assembly);
Assert.Equal("my", sut.GetName<MyType>()); Assert.Equal("my", sut.GetName<MyType>());
Assert.Equal("my", sut.GetName(typeof(MyType))); Assert.Equal("my", sut.GetName(typeof(MyType)));
@ -66,7 +66,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_register_event_type_from_assembly() public void Should_register_event_type_from_assembly()
{ {
sut.Map(typeof(TypeNameRegistryTests).GetTypeInfo().Assembly); sut.MapUnmapped(typeof(TypeNameRegistryTests).GetTypeInfo().Assembly);
Assert.Equal("MyAddedEventV2", sut.GetName<MyAdded>()); Assert.Equal("MyAddedEventV2", sut.GetName<MyAdded>());
Assert.Equal("MyAddedEventV2", sut.GetName(typeof(MyAdded))); Assert.Equal("MyAddedEventV2", sut.GetName(typeof(MyAdded)));
@ -78,7 +78,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_register_fallback_name() public void Should_register_fallback_name()
{ {
sut.Map(typeof(MyType)); sut.MapUnmapped(typeof(MyType));
sut.MapObsolete(typeof(MyType), "my-old"); sut.MapObsolete(typeof(MyType), "my-old");
Assert.Equal(typeof(MyType), sut.GetType("my")); Assert.Equal(typeof(MyType), sut.GetType("my"));
@ -88,24 +88,24 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_not_throw_exception_if_type_is_already_registered_with_same_name() public void Should_not_throw_exception_if_type_is_already_registered_with_same_name()
{ {
sut.Map(typeof(long), "long"); sut.MapUnmapped(typeof(long), "long");
sut.Map(typeof(long), "long"); sut.MapUnmapped(typeof(long), "long");
} }
[Fact] [Fact]
public void Should_throw_exception_if_type_is_already_registered() public void Should_throw_exception_if_type_is_already_registered()
{ {
sut.Map(typeof(long), "long"); sut.MapUnmapped(typeof(long), "long");
Assert.Throws<ArgumentException>(() => sut.Map(typeof(long), "longer")); Assert.Throws<ArgumentException>(() => sut.MapUnmapped(typeof(long), "longer"));
} }
[Fact] [Fact]
public void Should_throw_exception_if_name_is_already_registered() public void Should_throw_exception_if_name_is_already_registered()
{ {
sut.Map(typeof(short), "short"); sut.MapUnmapped(typeof(short), "short");
Assert.Throws<ArgumentException>(() => sut.Map(typeof(byte), "short")); Assert.Throws<ArgumentException>(() => sut.MapUnmapped(typeof(byte), "short"));
} }
[Fact] [Fact]

Loading…
Cancel
Save