Browse Source

Temporary commit.

pull/306/head
Sebastian 8 years ago
parent
commit
753419d07e
  1. 4
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchema.cs
  2. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs
  3. 24
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
  4. 1
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  5. 7
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  6. 4
      src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs
  7. 3
      src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs
  8. 44
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  9. 35
      src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs
  10. 3
      src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs
  11. 4
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  12. 42
      src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs
  13. 7
      src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs
  14. 7
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs
  15. 2
      src/Squidex.Domain.Apps.Events/Contents/ContentStatusChanged.cs
  16. 62
      src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  17. 2
      src/Squidex.Infrastructure/ValidationException.cs
  18. 10
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs
  19. 3
      src/Squidex/Config/Domain/RuleServices.cs
  20. 2
      src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs
  21. 21
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html
  22. 26
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
  23. 2
      src/Squidex/app/shared/state/rules.state.ts
  24. 43
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs
  25. 77
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs
  26. 72
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs
  27. 67
      tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs

4
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchema.cs

@ -22,5 +22,9 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers
public bool SendPublish { get; set; }
public bool SendUnpublish { get; set; }
public bool SendArchived { get; set; }
public bool SendRestore { get; set; }
}
}

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs

@ -9,9 +9,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public enum EnrichedContentEventType
{
Archived,
Created,
Deleted,
Published,
Restored,
Unpublished,
Updated
}

24
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs

@ -17,7 +17,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
protected override bool Triggers(Envelope<AppEvent> @event, ContentChangedTrigger trigger)
{
if (trigger.HandleAll && @event.Payload is ContentEvent)
if (trigger.HandleAll &&
@event.Payload is ContentEvent &&
!(@event.Payload is ContentChangesPublished) &&
!(@event.Payload is ContentChangesDiscarded) &&
!(@event.Payload is ContentUpdateProposed))
{
return true;
}
@ -44,21 +48,33 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
private static bool MatchsType(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return
IsArchived(schema, @event) ||
IsCreate(schema, @event) ||
IsUpdate(schema, @event) ||
IsDelete(schema, @event) ||
IsPublished(schema, @event) ||
IsRestored(schema, @event) ||
IsUpdate(schema, @event) ||
IsUnpublished(schema, @event);
}
private static bool IsPublished(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return schema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published;
return schema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Published;
}
private static bool IsRestored(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return schema.SendRestore && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Restored;
}
private static bool IsArchived(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return schema.SendArchived && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Archived;
}
private static bool IsUnpublished(ContentChangedTriggerSchema schema, SchemaEvent @event)
{
return schema.SendUnpublish && @event is ContentStatusChanged statusChanged && statusChanged.Status != Status.Published;
return schema.SendUnpublish && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Unpublished;
}
private static bool IsCreate(ContentChangedTriggerSchema schema, SchemaEvent @event)

1
src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -31,6 +31,7 @@ namespace Squidex.Domain.Apps.Entities
Guard.NotNull(localCache, nameof(localCache));
this.grainFactory = grainFactory;
this.localCache = localCache;
}

7
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -57,13 +57,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.initialPatterns = initialPatterns;
}
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotArchived();

4
src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs

@ -140,9 +140,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
ApplySnapshot(Snapshot.Apply(@event));
}
public Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Empty)
public Task<J<IAssetEntity>> GetStateAsync()
{
return J.AsTask<IAssetEntity>(GetSnapshot(version));
return J.AsTask<IAssetEntity>(Snapshot);
}
}
}

3
src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -14,6 +13,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
public interface IAssetGrain : IDomainObjectGrain
{
Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Any);
Task<J<IAssetEntity>> GetStateAsync();
}
}

44
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -117,9 +117,28 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
else
{
await ctx.ExecuteScriptAsync(x => x.ScriptChange, c.Status, c, Snapshot.Data);
ChangeStatus(c);
var reason = StatusChange.Published;
if (c.Status == Status.Published)
{
reason = StatusChange.Published;
}
else if (c.Status == Status.Archived)
{
reason = StatusChange.Archived;
}
else if (Snapshot.Status == Status.Published)
{
reason = StatusChange.Unpublished;
}
else
{
reason = StatusChange.Restored;
}
await ctx.ExecuteScriptAsync(x => x.ScriptChange, reason, c, Snapshot.Data);
ChangeStatus(c, reason);
}
}
}
@ -206,7 +225,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (command.Publish)
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Status = Status.Published }));
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published }));
}
}
@ -245,9 +264,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));
}
public void ChangeStatus(ChangeContentStatus command)
public void ChangeStatus(ChangeContentStatus command, StatusChange reason)
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged()));
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Change = reason }));
}
private void RaiseEvent(SchemaEvent @event)
@ -281,19 +300,16 @@ namespace Squidex.Domain.Apps.Entities.Contents
private async Task<ContentOperationContext> CreateContext(Guid appId, Guid schemaId, Func<string> message)
{
var operationContext =
await ContentOperationContext.CreateAsync(appId, schemaId,
appProvider,
assetRepository,
contentRepository,
scriptEngine,
message);
await ContentOperationContext.CreateAsync(
appId, schemaId,
appProvider, assetRepository, contentRepository, scriptEngine, message);
return operationContext;
}
public Task<J<IContentEntity>> GetStateAsync(long version = -2)
public Task<J<IContentEntity>> GetStateAsync()
{
return J.AsTask<IContentEntity>(GetSnapshot(version));
return J.AsTask<IContentEntity>(Snapshot);
}
}
}

35
src/Squidex.Domain.Apps.Entities/Contents/ContentVersionLoader.cs

@ -7,38 +7,53 @@
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentVersionLoader : IContentVersionLoader
{
private readonly IGrainFactory grainFactory;
private readonly IStore<Guid> store;
private readonly FieldRegistry registry;
public ContentVersionLoader(IGrainFactory grainFactory)
public ContentVersionLoader(IStore<Guid> store, FieldRegistry registry)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(store, nameof(store));
Guard.NotNull(registry, nameof(registry));
this.grainFactory = grainFactory;
this.store = store;
this.registry = registry;
}
public async Task<IContentEntity> LoadAsync(Guid id, long version)
{
using (Profiler.TraceMethod<ContentVersionLoader>())
{
var grain = grainFactory.GetGrain<IContentGrain>(id);
var content = new ContentState();
var persistence = store.WithEventSourcing<ContentGrain, Guid>(id, e =>
{
if (content.Version < version)
{
content = content.Apply(e);
content.Version++;
}
});
var content = await grain.GetStateAsync(version);
await persistence.ReadAsync();
if (content.Value == null || content.Value.Version != version)
if (content.Version != version)
{
throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity));
}
return content.Value;
return content;
}
}
}
}
}

3
src/Squidex.Domain.Apps.Entities/Contents/IContentGrain.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
@ -14,6 +13,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IContentGrain : IDomainObjectGrain
{
Task<J<IContentEntity>> GetStateAsync(long version = EtagVersion.Any);
Task<J<IContentEntity>> GetStateAsync();
}
}

4
src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs

@ -32,10 +32,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
public NamedContentData DataDraft { get; set; }
[JsonProperty]
public Status Status { get; set; }
public ScheduleJob ScheduleJob { get; set; }
[JsonProperty]
public ScheduleJob ScheduleJob { get; set; }
public Status Status { get; set; }
[JsonProperty]
public bool IsPending { get; set; }

42
src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using NodaTime;
using Orleans;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules;
@ -31,19 +30,16 @@ namespace Squidex.Domain.Apps.Entities.Rules
private readonly IGrainFactory grainFactory;
private readonly IMemoryCache cache;
private readonly IUserResolver userResolver;
private readonly IClock clock;
public EventEnricher(IGrainFactory grainFactory, IMemoryCache cache, IUserResolver userResolver, IClock clock)
public EventEnricher(IGrainFactory grainFactory, IMemoryCache cache, IUserResolver userResolver)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(cache, nameof(cache));
Guard.NotNull(clock, nameof(clock));
Guard.NotNull(userResolver, nameof(userResolver));
this.userResolver = userResolver;
this.cache = cache;
this.clock = clock;
this.grainFactory = grainFactory;
this.cache = cache;
this.userResolver = userResolver;
}
public async Task<EnrichedEvent> EnrichAsync(Envelope<AppEvent> @event)
@ -80,9 +76,10 @@ namespace Squidex.Domain.Apps.Entities.Rules
var asset =
(await grainFactory
.GetGrain<IAssetGrain>(assetEvent.AssetId)
.GetStateAsync(@event.Headers.EventStreamNumber())).Value;
.GetStateAsync()).Value;
SimpleMapper.Map(asset, result);
SimpleMapper.Map(assetEvent, result);
switch (assetEvent)
{
@ -108,12 +105,14 @@ namespace Squidex.Domain.Apps.Entities.Rules
var content =
(await grainFactory
.GetGrain<IContentGrain>(contentEvent.ContentId)
.GetStateAsync(@event.Headers.EventStreamNumber())).Value;
.GetStateAsync()).Value;
SimpleMapper.Map(content, result);
result.Data = content.Data ?? content.DataDraft;
SimpleMapper.Map(contentEvent, result);
switch (contentEvent)
{
case ContentCreated _:
@ -122,17 +121,25 @@ namespace Squidex.Domain.Apps.Entities.Rules
case ContentDeleted _:
result.Type = EnrichedContentEventType.Deleted;
break;
case ContentChangesPublished _:
case ContentUpdated _:
result.Type = EnrichedContentEventType.Updated;
break;
case ContentStatusChanged contentStatusChanged:
if (contentStatusChanged.Status == Status.Published)
{
result.Type = EnrichedContentEventType.Published;
}
else
switch (contentStatusChanged.Change)
{
result.Type = EnrichedContentEventType.Unpublished;
case StatusChange.Published:
result.Type = EnrichedContentEventType.Published;
break;
case StatusChange.Unpublished:
result.Type = EnrichedContentEventType.Unpublished;
break;
case StatusChange.Archived:
result.Type = EnrichedContentEventType.Archived;
break;
case StatusChange.Restored:
result.Type = EnrichedContentEventType.Restored;
break;
}
break;
@ -143,10 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
private async Task EnrichDefaultAsync(EnrichedEvent result, Envelope<AppEvent> @event)
{
result.Timestamp =
@event.Headers.Contains(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() :
clock.GetCurrentInstant();
result.Timestamp = @event.Headers.Timestamp();
if (@event.Payload is SquidexEvent squidexEvent)
{

7
src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs

@ -34,13 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Rules
this.appProvider = appProvider;
}
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();

7
src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs

@ -40,13 +40,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas
this.registry = registry;
}
public override Task OnActivateAsync()
{
CleanupOldSnapshots();
return base.OnActivateAsync();
}
protected override Task<object> ExecuteAsync(IAggregateCommand command)
{
VerifyNotDeleted();

2
src/Squidex.Domain.Apps.Events/Contents/ContentStatusChanged.cs

@ -13,6 +13,8 @@ namespace Squidex.Domain.Apps.Events.Contents
[EventType(nameof(ContentStatusChanged))]
public sealed class ContentStatusChanged : ContentEvent
{
public StatusChange? Change { get; set; }
public Status Status { get; set; }
}
}

62
src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -21,9 +21,8 @@ namespace Squidex.Infrastructure.Commands
private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>();
private readonly IStore<Guid> store;
private readonly ISemanticLog log;
private readonly List<T> snapshots = new List<T> { new T { Version = EtagVersion.Empty } };
private bool cleanup;
private Guid id;
private T snapshot = new T { Version = EtagVersion.Empty };
private IPersistence<T> persistence;
public Guid Id
@ -33,17 +32,17 @@ namespace Squidex.Infrastructure.Commands
public long Version
{
get { return snapshots.Count - 2; }
get { return snapshot.Version; }
}
public long NewVersion
{
get { return Version; }
get { return snapshot.Version + uncomittedEvents.Count; }
}
public T Snapshot
{
get { return snapshots[snapshots.Count - 1]; }
get { return snapshot; }
}
protected DomainObjectGrain(IStore<Guid> store, ISemanticLog log)
@ -56,31 +55,6 @@ namespace Squidex.Infrastructure.Commands
this.log = log;
}
public void CleanupOldSnapshots()
{
cleanup = true;
}
public T GetSnapshot(long version)
{
if (version == EtagVersion.Any)
{
return Snapshot;
}
if (version == EtagVersion.Empty)
{
return snapshots[0];
}
if (version >= 0 && version < snapshots.Count - 1)
{
return snapshots[(int)version + 1];
}
return default(T);
}
public override async Task OnActivateAsync(Guid key)
{
using (log.MeasureInformation(w => w
@ -122,11 +96,9 @@ namespace Squidex.Infrastructure.Commands
uncomittedEvents.Clear();
}
public virtual void ApplySnapshot(T snapshot)
public virtual void ApplySnapshot(T newSnapshot)
{
snapshot.Version = snapshots.Count - 1;
snapshots.Add(snapshot);
snapshot = newSnapshot;
}
public virtual void ApplyEvent(Envelope<IEvent> @event)
@ -200,8 +172,7 @@ namespace Squidex.Infrastructure.Commands
throw new DomainException("Object has already been created.");
}
var size = snapshots.Count;
var previousSnapshot = snapshot;
try
{
var result = await handler(command);
@ -210,8 +181,10 @@ namespace Squidex.Infrastructure.Commands
if (events.Length > 0)
{
snapshot.Version = NewVersion;
await persistence.WriteEventsAsync(events);
await persistence.WriteSnapshotAsync(Snapshot);
await persistence.WriteSnapshotAsync(snapshot);
}
if (result == null)
@ -230,23 +203,12 @@ namespace Squidex.Infrastructure.Commands
}
catch
{
while (snapshots.Count > size)
{
snapshots.RemoveAt(snapshots.Count - 1);
}
snapshot = previousSnapshot;
throw;
}
finally
{
if (cleanup)
{
for (var i = 0; i < snapshots.Count - 1; i++)
{
snapshots[i] = default(T);
}
}
uncomittedEvents.Clear();
}
}
@ -260,4 +222,4 @@ namespace Squidex.Infrastructure.Commands
protected abstract Task<object> ExecuteAsync(IAggregateCommand command);
}
}
}

2
src/Squidex.Infrastructure/ValidationException.cs

@ -21,7 +21,7 @@ namespace Squidex.Infrastructure
public IReadOnlyList<ValidationError> Errors
{
get { return errors; }
get { return errors ?? FallbackErrors; }
}
public string Summary { get; }

10
src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs

@ -40,5 +40,15 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers
/// Determines whether to handle the event when a content is unpublished.
/// </summary>
public bool SendUnpublish { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is archived.
/// </summary>
public bool SendArchived { get; set; }
/// <summary>
/// Determines whether to handle the event when a content is restored.
/// </summary>
public bool SendRestore { get; set; }
}
}

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

@ -18,6 +18,9 @@ namespace Squidex.Config.Domain
{
public static void AddMyRuleServices(this IServiceCollection services)
{
services.AddSingletonAs<EventEnricher>()
.As<IEventEnricher>();
services.AddSingletonAs<AssetChangedTriggerHandler>()
.As<IRuleTriggerHandler>();

2
src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs

@ -54,7 +54,7 @@ namespace Squidex.Pipeline
private static IActionResult OnValidationException(ValidationException ex)
{
return ErrorResult(400, new ErrorDto { Message = ex.Summary, Details = ex.Errors.Select(e => e.Message).ToArray() });
return ErrorResult(400, new ErrorDto { Message = ex.Summary, Details = ex.Errors?.Select(e => e.Message).ToArray() });
}
private static IActionResult ErrorResult(int statusCode, ErrorDto error)

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

@ -10,6 +10,9 @@
<col style="width: 40px" />
<col style="width: 40px" />
<col style="width: 40px" />
<col style="width: 40px" />
<col style="width: 40px" />
<col style="width: 40px" />
</colgroup>
<tr>
@ -31,6 +34,15 @@
<th class="text-center">
<div class="rotated-label">Published</div>
</th>
<th class="text-center">
<div class="rotated-label">Unpublished</div>
</th>
<th class="text-center">
<div class="rotated-label">Archived</div>
</th>
<th class="text-center">
<div class="rotated-label">Restored</div>
</th>
<th></th>
</tr>
@ -53,6 +65,15 @@
<td class="text-center" title="Published">
<input type="checkbox" [ngModel]="schema.sendPublish" (ngModelChange)="toggle(schema, 'sendPublish')" />
</td>
<td class="text-center" title="Unpublished">
<input type="checkbox" [ngModel]="schema.sendUnpublish" (ngModelChange)="toggle(schema, 'sendUnpublish')" />
</td>
<td class="text-center" title="Archived">
<input type="checkbox" [ngModel]="schema.sendArchived" (ngModelChange)="toggle(schema, 'sendArchived')" />
</td>
<td class="text-center" title="Restored">
<input type="checkbox" [ngModel]="schema.sendRestored" (ngModelChange)="toggle(schema, 'sendRestored')" />
</td>
<td class="text-center">
<button type="button" class="btn btn-link btn-secondary" (click)="removeSchema(schema)">
<i class="icon-close"></i>

26
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts

@ -21,6 +21,9 @@ export interface TriggerSchemaForm {
sendUpdate: boolean;
sendDelete: boolean;
sendPublish: boolean;
sendUnpublish: boolean;
sendArchive: boolean;
sendRestore: boolean;
}
@Component({
@ -71,7 +74,10 @@ export class ContentChangedTriggerComponent implements OnInit {
sendCreate: triggerSchema.sendCreate,
sendUpdate: triggerSchema.sendUpdate,
sendDelete: triggerSchema.sendDelete,
sendPublish: triggerSchema.sendPublish
sendPublish: triggerSchema.sendPublish,
sendUnpublish: triggerSchema.sendUnpublish,
sendArchive: triggerSchema.sendArchive,
sendRestore: triggerSchema.sendRestore
});
} else {
return null;
@ -104,7 +110,10 @@ export class ContentChangedTriggerComponent implements OnInit {
sendCreate: false,
sendUpdate: false,
sendDelete: false,
sendPublish: false
sendPublish: false,
sendUnpublish: false,
sendArchive: false,
sendRestore: false
})).sortByStringAsc(x => x.schema.name);
this.updateValue();
@ -137,7 +146,10 @@ export class ContentChangedTriggerComponent implements OnInit {
sendCreate: s.sendCreate,
sendUpdate: s.sendUpdate,
sendDelete: s.sendDelete,
sendPublish: s.sendPublish
sendPublish: s.sendPublish,
sendUnpublish: s.sendUnpublish,
sendArchive: s.sendArchive,
sendRestore: s.sendRestore
};
});
@ -150,6 +162,9 @@ export class ContentChangedTriggerComponent implements OnInit {
schemaForm.sendUpdate = value;
schemaForm.sendDelete = value;
schemaForm.sendPublish = value;
schemaForm.sendUnpublish = value;
schemaForm.sendArchive = value;
schemaForm.sendRestore = value;
return schemaForm;
}
@ -159,7 +174,10 @@ export class ContentChangedTriggerComponent implements OnInit {
schemaForm.sendCreate &&
schemaForm.sendUpdate &&
schemaForm.sendDelete &&
schemaForm.sendPublish;
schemaForm.sendPublish &&
schemaForm.sendUnpublish &&
schemaForm.sendArchive &&
schemaForm.sendRestore;
return schemaForm;
}

2
src/Squidex/app/shared/state/rules.state.ts

@ -158,7 +158,7 @@ const updateTrigger = (rule: RuleDto, trigger: any, user: string, version: Versi
const updateAction = (rule: RuleDto, action: any, user: string, version: Version, now?: DateTime) =>
rule.with({
action,
actionType: action.triggerType,
actionType: action.actionType,
lastModified: now || DateTime.now(),
lastModifiedBy: user,
version

43
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs

@ -32,19 +32,23 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
public static IEnumerable<object[]> TestData = new[]
{
new object[] { 0, 1, 1, 1, 1, new RuleCreated() },
new object[] { 0, 1, 1, 1, 1, new ContentCreated { SchemaId = SchemaNonMatch } },
new object[] { 1, 1, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } },
new object[] { 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } },
new object[] { 1, 0, 1, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } },
new object[] { 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } },
new object[] { 1, 0, 0, 1, 0, new ContentDeleted { SchemaId = SchemaMatch } },
new object[] { 0, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } },
new object[] { 1, 0, 0, 0, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Published } },
new object[] { 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Published } },
new object[] { 0, 1, 1, 1, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Archived } },
new object[] { 0, 1, 1, 1, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Draft } },
new object[] { 0, 1, 1, 1, 1, new SchemaCreated { SchemaId = SchemaNonMatch } }
new object[] { 0, 1, 1, 1, 1, 0, 0, 0, new RuleCreated() },
new object[] { 0, 1, 1, 1, 1, 0, 0, 0, new ContentCreated { SchemaId = SchemaNonMatch } },
new object[] { 1, 1, 0, 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } },
new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } },
new object[] { 1, 0, 1, 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } },
new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } },
new object[] { 1, 0, 0, 1, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } },
new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } },
new object[] { 1, 1, 1, 1, 0, 0, 1, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Archived } },
new object[] { 0, 1, 1, 1, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Archived } },
new object[] { 1, 0, 0, 0, 0, 0, 0, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Restored } },
new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Restored } },
new object[] { 1, 0, 0, 0, 1, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Published } },
new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Published } },
new object[] { 1, 1, 1, 1, 0, 1, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Unpublished } },
new object[] { 0, 1, 1, 1, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Unpublished } },
new object[] { 0, 1, 1, 1, 1, 0, 0, 0, new SchemaCreated { SchemaId = SchemaNonMatch } }
};
[Fact]
@ -69,7 +73,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
[Theory]
[MemberData(nameof(TestData))]
public void Should_return_result_depending_on_event(int expected, int sendCreate, int sendUpdate, int sendDelete, int sendPublish, AppEvent @event)
public void Should_return_result_depending_on_event(int expected,
int sendCreate,
int sendUpdate,
int sendDelete,
int sendPublish,
int sendUnpublish,
int sendArchive,
int sendRestore,
AppEvent @event)
{
var trigger = new ContentChangedTrigger
{
@ -80,6 +92,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers
SendUpdate = sendUpdate == 1,
SendDelete = sendDelete == 1,
SendPublish = sendPublish == 1,
SendUnpublish = sendUnpublish == 1,
SendArchived = sendArchive == 1,
SendRestore = sendRestore == 1,
SchemaId = SchemaMatch.Id
})
};

77
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs

@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data }),
CreateContentEvent(new ContentStatusChanged { Status = Status.Published })
CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published })
);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>"))
@ -312,7 +312,75 @@ namespace Squidex.Domain.Apps.Entities.Contents
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Published })
CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published })
);
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
.MustHaveHappened();
}
[Fact]
public async Task ChangedStatus_should_create_events_and_update_state_when_archived()
{
var command = new ChangeContentStatus { Status = Status.Archived };
await ExecuteCreateAsync();
var result = await sut.ExecuteAsync(CreateContentCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal(Status.Archived, sut.Snapshot.Status);
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived, Change = StatusChange.Archived })
);
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
.MustHaveHappened();
}
[Fact]
public async Task ChangedStatus_should_create_events_and_update_state_when_unpublished()
{
var command = new ChangeContentStatus { Status = Status.Draft };
await ExecuteCreateAsync();
await ExecutePublishAsync();
var result = await sut.ExecuteAsync(CreateContentCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(2));
Assert.Equal(Status.Draft, sut.Snapshot.Status);
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished })
);
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
.MustHaveHappened();
}
[Fact]
public async Task ChangedStatus_should_create_events_and_update_state_when_restored()
{
var command = new ChangeContentStatus { Status = Status.Draft };
await ExecuteCreateAsync();
await ExecuteArchiveAsync();
var result = await sut.ExecuteAsync(CreateContentCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(2));
Assert.Equal(Status.Draft, sut.Snapshot.Status);
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Restored })
);
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
@ -459,6 +527,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
return sut.ExecuteAsync(CreateContentCommand(new DeleteContent()));
}
private Task ExecuteArchiveAsync()
{
return sut.ExecuteAsync(CreateContentCommand(new ChangeContentStatus { Status = Status.Archived }));
}
private Task ExecutePublishAsync()
{
return sut.ExecuteAsync(CreateContentCommand(new ChangeContentStatus { Status = Status.Published }));

72
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs

@ -1,72 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentVersionLoaderTests
{
private readonly IGrainFactory grainFactory = A.Fake<IGrainFactory>();
private readonly IContentGrain grain = A.Fake<IContentGrain>();
private readonly Guid id = Guid.NewGuid();
private readonly ContentVersionLoader sut;
public ContentVersionLoaderTests()
{
A.CallTo(() => grainFactory.GetGrain<IContentGrain>(id, null))
.Returns(grain);
sut = new ContentVersionLoader(grainFactory);
}
[Fact]
public async Task Should_throw_exception_if_no_state_returned()
{
A.CallTo(() => grain.GetStateAsync(10))
.Returns(new J<IContentEntity>(null));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
}
[Fact]
public async Task Should_throw_exception_if_state_has_other_version()
{
var entity = A.Fake<IContentEntity>();
A.CallTo(() => entity.Version)
.Returns(5);
A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
}
[Fact]
public async Task Should_return_content_from_state()
{
var entity = A.Fake<IContentEntity>();
A.CallTo(() => entity.Version)
.Returns(10);
A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity));
var result = await sut.LoadAsync(id, 10);
Assert.Same(entity, result);
}
}
}

67
tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
@ -125,72 +124,6 @@ namespace Squidex.Infrastructure.Commands
Assert.Equal(EtagVersion.Empty, sut.Version);
}
[Fact]
public async Task Should_get_latestet_version_when_requesting_state_with_any()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
var result = sut.GetSnapshot(EtagVersion.Any);
result.Should().BeEquivalentTo(new MyDomainState { Value = 10, Version = 1 });
}
[Fact]
public async Task Should_get_empty_version_when_requesting_state_with_empty_version()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
var result = sut.GetSnapshot(EtagVersion.Empty);
result.Should().BeEquivalentTo(new MyDomainState { Value = 0, Version = -1 });
}
[Fact]
public async Task Should_get_specific_version_when_requesting_state_with_specific_version()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
sut.GetSnapshot(0).Should().BeEquivalentTo(new MyDomainState { Value = 5, Version = 0 });
sut.GetSnapshot(1).Should().BeEquivalentTo(new MyDomainState { Value = 10, Version = 1 });
}
[Fact]
public async Task Should_automatically_cleanup_old_snapshots_when_enabled()
{
await SetupEmptyAsync();
sut.CleanupOldSnapshots();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 15 }));
Assert.Null(sut.GetSnapshot(0));
Assert.Null(sut.GetSnapshot(1));
Assert.NotNull(sut.GetSnapshot(2));
}
[Fact]
public async Task Should_get_null_state_when_requesting_state_with_invalid_version()
{
await SetupEmptyAsync();
await sut.ExecuteAsync(C(new CreateAuto { Value = 5 }));
await sut.ExecuteAsync(C(new UpdateAuto { Value = 10 }));
Assert.Null(sut.GetSnapshot(-3));
Assert.Null(sut.GetSnapshot(2));
}
[Fact]
public async Task Should_write_state_and_events_when_created()
{

Loading…
Cancel
Save