Browse Source

Temporary

pull/377/head
Sebastian Stehle 7 years ago
parent
commit
337abb03c4
  1. 5
      src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
  2. 16
      src/Squidex.Domain.Apps.Core.Model/Contents/StatusColors.cs
  3. 23
      src/Squidex.Domain.Apps.Core.Model/Contents/StatusInfo.cs
  4. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  5. 9
      src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs
  6. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  7. 26
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  8. 61
      src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs
  9. 8
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs
  10. 2
      src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs
  11. 8
      src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs
  12. 11
      src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs
  13. 10
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  14. 6
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs
  15. 4
      src/Squidex.Domain.Apps.Events/Contents/ContentCreated.cs
  16. 4
      src/Squidex.Domain.Apps.Events/Contents/ContentStatusChanged.cs
  17. 2
      src/Squidex.Domain.Apps.Events/Contents/ContentStatusScheduled.cs
  18. 24
      src/Squidex.Web/Resource.cs
  19. 4
      src/Squidex.Web/ResourceLink.cs
  20. 11
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  21. 4
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  22. 32
      src/Squidex/Areas/Api/Controllers/Contents/Models/StatusInfoDto.cs
  23. 3
      src/Squidex/Config/Domain/StoreServices.cs
  24. 2
      src/Squidex/app/features/content/pages/contents/contents-filters-page.component.html
  25. 2
      src/Squidex/app/features/content/pages/contents/contents-filters-page.component.scss
  26. 2
      src/Squidex/app/framework/utils/hateos.ts
  27. 6
      src/Squidex/app/shared/services/contents.service.spec.ts
  28. 6
      src/Squidex/app/shared/services/contents.service.ts
  29. 14
      src/Squidex/app/shared/state/contents.state.ts
  30. 33
      tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs
  31. 95
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs
  32. 37
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs
  33. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  34. 3
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  35. 8
      tools/Migrate_01/MigrationPath.cs
  36. 42
      tools/Migrate_01/Migrations/MongoDb/CreateStatusColors.cs
  37. 7
      tools/Migrate_01/OldEvents/AppPlanChanged.cs
  38. 51
      tools/Migrate_01/OldEvents/ContentCreated.cs
  39. 60
      tools/Migrate_01/OldEvents/ContentStatusChanged.cs
  40. 52
      tools/Migrate_01/OldEvents/ContentStatusScheduled.cs

5
src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
using System; using System;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
@ -16,6 +15,8 @@ namespace Squidex.Domain.Apps.Core.Contents
public static readonly Status Draft = new Status("Draft"); public static readonly Status Draft = new Status("Draft");
public static readonly Status Published = new Status("Published"); public static readonly Status Published = new Status("Published");
public const string FallbackColor = "#8091a5";
private readonly string name; private readonly string name;
public string Name public string Name
@ -45,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public override string ToString() public override string ToString()
{ {
return name; return Name;
} }
public static bool operator ==(Status lhs, Status rhs) public static bool operator ==(Status lhs, Status rhs)

16
src/Squidex.Domain.Apps.Core.Model/Contents/StatusColors.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Contents
{
public static class StatusColors
{
public const string Archived = "#eb3142";
public const string Draft = "#8091a5";
public const string Published = "#4bb958";
}
}

23
src/Squidex.Domain.Apps.Core.Model/Contents/StatusInfo.cs

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Contents
{
public sealed class StatusInfo
{
public Status Status { get; }
public string Color { get; }
public StatusInfo(Status status, string color)
{
Status = status;
Color = color;
}
}
}

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -53,6 +53,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonElement("ss")] [BsonElement("ss")]
public Status Status { get; set; } public Status Status { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sc")]
public string StatusColor { get; set; }
[BsonIgnoreIfNull] [BsonIgnoreIfNull]
[BsonElement("do")] [BsonElement("do")]
[BsonJson] [BsonJson]

9
src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs

@ -19,15 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppHistoryEventsCreator(TypeNameRegistry typeNameRegistry) public AppHistoryEventsCreator(TypeNameRegistry typeNameRegistry)
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage("AppContributorAssignedEvent",
"assigned {user:[Contributor]} as {[Role]}");
AddEventMessage("AppClientUpdatedEvent",
"updated client {[Id]}");
AddEventMessage("AppPlanChanged",
"changed plan to {[Plan]}");
AddEventMessage<AppContributorAssigned>( AddEventMessage<AppContributorAssigned>(
"assigned {user:[Contributor]} as {[Role]}"); "assigned {user:[Contributor]} as {[Role]}");

2
src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs

@ -38,6 +38,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
public Status Status { get; set; } public Status Status { get; set; }
public string StatusColor { get; set; }
public bool IsPending { get; set; } public bool IsPending { get; set; }
} }
} }

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

@ -83,9 +83,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ctx.ExecuteScriptAsync(s => s.Change, "Published", c, c.Data); await ctx.ExecuteScriptAsync(s => s.Change, "Published", c, c.Data);
} }
var status = await contentWorkflow.GetInitialStatusAsync(ctx.Schema); var statusInfo = await contentWorkflow.GetInitialStatusAsync(ctx.Schema);
Create(c, status); Create(c, statusInfo.Status, statusInfo.Color);
return Snapshot; return Snapshot;
}); });
@ -127,6 +127,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
} }
else else
{ {
var statusInfo = await contentWorkflow.GetInfoAsync(c.Status);
StatusChange reason; StatusChange reason;
if (c.Status == Status.Published) if (c.Status == Status.Published)
@ -144,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ctx.ExecuteScriptAsync(s => s.Change, reason, c, Snapshot.Data); await ctx.ExecuteScriptAsync(s => s.Change, reason, c, Snapshot.Data);
ChangeStatus(c, reason); ChangeStatus(c, reason, statusInfo.Color);
} }
} }
} }
@ -229,13 +231,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
return Snapshot; return Snapshot;
} }
public void Create(CreateContent command, Status status) public void Create(CreateContent command, Status status, string color)
{ {
RaiseEvent(SimpleMapper.Map(command, new ContentCreated { Status = status })); RaiseEvent(SimpleMapper.Map(command, new ContentCreated
{
Status = status,
StatusColor = color
}));
if (command.Publish) if (command.Publish)
{ {
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Status = Status.Published })); RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged
{
Status = Status.Published,
StatusColor = StatusColors.Published
}));
} }
} }
@ -274,9 +284,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value })); RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));
} }
public void ChangeStatus(ChangeContentStatus command, StatusChange change) public void ChangeStatus(ChangeContentStatus command, StatusChange change, string color)
{ {
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Change = change })); RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged { Change = change, StatusColor = color }));
} }
private void RaiseEvent(SchemaEvent @event) private void RaiseEvent(SchemaEvent @event)

61
src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs

@ -11,42 +11,77 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class DefaultContentWorkflow : IContentWorkflow public sealed class DefaultContentWorkflow : IContentWorkflow
{ {
private static readonly Status[] All = { Status.Archived, Status.Draft, Status.Published }; private static readonly StatusInfo InfoArchived = new StatusInfo(Status.Archived, StatusColors.Archived);
private static readonly StatusInfo InfoDraft = new StatusInfo(Status.Draft, StatusColors.Draft);
private static readonly StatusInfo InfoPublished = new StatusInfo(Status.Published, StatusColors.Published);
private static readonly Dictionary<Status, Status[]> Flow = new Dictionary<Status, Status[]> private static readonly StatusInfo[] All =
{ {
[Status.Draft] = new[] { Status.Archived, Status.Published }, InfoArchived,
[Status.Archived] = new[] { Status.Draft }, InfoDraft,
[Status.Published] = new[] { Status.Draft, Status.Archived } InfoPublished
}; };
public Task<Status> GetInitialStatusAsync(ISchemaEntity schema) private static readonly Dictionary<Status, (StatusInfo Info, StatusInfo[] Transitions)> Flow =
new Dictionary<Status, (StatusInfo Info, StatusInfo[] Transitions)>
{
[Status.Archived] = (InfoArchived, new[]
{
InfoDraft
}),
[Status.Draft] = (InfoDraft, new[]
{
InfoArchived,
InfoPublished
}),
[Status.Published] = (InfoPublished, new[]
{
InfoDraft,
InfoArchived
})
};
public Task<StatusInfo> GetInitialStatusAsync(ISchemaEntity schema)
{ {
return Task.FromResult(Status.Draft); var result = InfoDraft;
return Task.FromResult(result);
} }
public Task<bool> CanMoveToAsync(IContentEntity content, Status next) public Task<bool> CanMoveToAsync(IContentEntity content, Status next)
{ {
return Task.FromResult(Flow.TryGetValue(content.Status, out var state) && state.Contains(next)); var result = Flow.TryGetValue(content.Status, out var step) && step.Transitions.Any(x => x.Status == next);
return Task.FromResult(result);
} }
public Task<bool> CanUpdateAsync(IContentEntity content) public Task<bool> CanUpdateAsync(IContentEntity content)
{ {
return Task.FromResult(content.Status != Status.Archived); var result = content.Status != Status.Archived;
return Task.FromResult(result);
} }
public Task<Status[]> GetNextsAsync(IContentEntity content) public Task<StatusInfo> GetInfoAsync(Status status)
{ {
return Task.FromResult(Flow.TryGetValue(content.Status, out var result) ? result : Array.Empty<Status>()); var result = Flow[status].Info;
return Task.FromResult(result);
}
public Task<StatusInfo[]> GetNextsAsync(IContentEntity content)
{
var result = Flow.TryGetValue(content.Status, out var step) ? step.Transitions : Array.Empty<StatusInfo>();
return Task.FromResult(result);
} }
public Task<Status[]> GetAllAsync(ISchemaEntity schema) public Task<StatusInfo[]> GetAllAsync(ISchemaEntity schema)
{ {
return Task.FromResult(All); return Task.FromResult(All);
} }

8
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs

@ -78,6 +78,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = $"The the status of the {schemaName} content." Description = $"The the status of the {schemaName} content."
}); });
AddField(new FieldType
{
Name = "statusColor",
ResolvedType = AllTypes.NonNullString,
Resolver = Resolve(x => x.StatusColor),
Description = $"The color status of the {schemaName} content."
});
AddField(new FieldType AddField(new FieldType
{ {
Name = "url", Name = "url",

2
src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs

@ -24,6 +24,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
Status Status { get; } Status Status { get; }
string StatusColor { get; }
ScheduleJob ScheduleJob { get; } ScheduleJob ScheduleJob { get; }
NamedContentData Data { get; } NamedContentData Data { get; }

8
src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs

@ -13,14 +13,16 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentWorkflow public interface IContentWorkflow
{ {
Task<Status> GetInitialStatusAsync(ISchemaEntity schema); Task<StatusInfo> GetInitialStatusAsync(ISchemaEntity schema);
Task<bool> CanMoveToAsync(IContentEntity content, Status next); Task<bool> CanMoveToAsync(IContentEntity content, Status next);
Task<bool> CanUpdateAsync(IContentEntity content); Task<bool> CanUpdateAsync(IContentEntity content);
Task<Status[]> GetNextsAsync(IContentEntity content); Task<StatusInfo> GetInfoAsync(Status status);
Task<Status[]> GetAllAsync(ISchemaEntity schema); Task<StatusInfo[]> GetNextsAsync(IContentEntity content);
Task<StatusInfo[]> GetAllAsync(ISchemaEntity schema);
} }
} }

11
src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs

@ -16,21 +16,24 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public Guid Id { get; } public Guid Id { get; }
public Instant DueTime { get; }
public Status Status { get; } public Status Status { get; }
public RefToken ScheduledBy { get; } public string StatusColor { get; set; }
public Instant DueTime { get; } public RefToken ScheduledBy { get; }
public ScheduleJob(Guid id, Status status, RefToken scheduledBy, Instant dueTime) public ScheduleJob(Guid id, Status status, string statusColor, RefToken scheduledBy, Instant dueTime)
{ {
Id = id; Id = id;
ScheduledBy = scheduledBy; ScheduledBy = scheduledBy;
Status = status; Status = status;
StatusColor = statusColor;
DueTime = dueTime; DueTime = dueTime;
} }
public static ScheduleJob Build(Status status, RefToken scheduledBy, Instant dueTime) public static ScheduleJob Build(Status status, string statusColor, RefToken scheduledBy, Instant dueTime)
{ {
return new ScheduleJob(Guid.NewGuid(), status, scheduledBy, dueTime); return new ScheduleJob(Guid.NewGuid(), status, scheduledBy, dueTime);
} }

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

@ -45,16 +45,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
[DataMember] [DataMember]
public Status Status { get; set; } public Status Status { get; set; }
[DataMember]
public string StatusColor { get; set; }
protected void On(ContentCreated @event) protected void On(ContentCreated @event)
{ {
SimpleMapper.Map(@event, this); SimpleMapper.Map(@event, this);
UpdateData(null, @event.Data, false); UpdateData(null, @event.Data, false);
if (Status == default)
{
Status = Status.Draft;
}
} }
protected void On(ContentChangesPublished @event) protected void On(ContentChangesPublished @event)
@ -68,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
{ {
ScheduleJob = null; ScheduleJob = null;
Status = @event.Status; SimpleMapper.Map(@event, this);
if (@event.Status == Status.Published) if (@event.Status == Status.Published)
{ {

6
src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs

@ -19,12 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public SchemaHistoryEventsCreator(TypeNameRegistry typeNameRegistry) public SchemaHistoryEventsCreator(TypeNameRegistry typeNameRegistry)
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage("SchemaCreatedEvent",
"created schema {[Name]}.");
AddEventMessage("ScriptsConfiguredEvent",
"configured script of schema {[Name]}.");
AddEventMessage<SchemaFieldsReordered>( AddEventMessage<SchemaFieldsReordered>(
"reordered fields of schema {[Name]}."); "reordered fields of schema {[Name]}.");

4
src/Squidex.Domain.Apps.Events/Contents/ContentCreated.cs

@ -10,11 +10,13 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Contents namespace Squidex.Domain.Apps.Events.Contents
{ {
[EventType(nameof(ContentCreated))] [EventType(nameof(ContentCreated), 2)]
public sealed class ContentCreated : ContentEvent public sealed class ContentCreated : ContentEvent
{ {
public Status Status { get; set; } public Status Status { get; set; }
public string StatusColor { get; set; }
public NamedContentData Data { get; set; } public NamedContentData Data { get; set; }
} }
} }

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

@ -10,11 +10,13 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Contents namespace Squidex.Domain.Apps.Events.Contents
{ {
[EventType(nameof(ContentStatusChanged))] [EventType(nameof(ContentStatusChanged), 2)]
public sealed class ContentStatusChanged : ContentEvent public sealed class ContentStatusChanged : ContentEvent
{ {
public StatusChange Change { get; set; } public StatusChange Change { get; set; }
public Status Status { get; set; } public Status Status { get; set; }
public string StatusColor { get; set; }
} }
} }

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

@ -16,6 +16,8 @@ namespace Squidex.Domain.Apps.Events.Contents
{ {
public Status Status { get; set; } public Status Status { get; set; }
public string StatusColor { get; set; }
public Instant DueTime { get; set; } public Instant DueTime { get; set; }
} }
} }

24
src/Squidex.Web/Resource.cs

@ -24,38 +24,38 @@ namespace Squidex.Web
AddGetLink("self", href); AddGetLink("self", href);
} }
public void AddGetLink(string rel, string href) public void AddGetLink(string rel, string href, string metadata = null)
{ {
AddLink(rel, "GET", href); AddLink(rel, "GET", href, metadata);
} }
public void AddPatchLink(string rel, string href) public void AddPatchLink(string rel, string href, string metadata = null)
{ {
AddLink(rel, "PATCH", href); AddLink(rel, "PATCH", href, metadata);
} }
public void AddPostLink(string rel, string href) public void AddPostLink(string rel, string href, string metadata = null)
{ {
AddLink(rel, "POST", href); AddLink(rel, "POST", href, metadata);
} }
public void AddPutLink(string rel, string href) public void AddPutLink(string rel, string href, string metadata = null)
{ {
AddLink(rel, "PUT", href); AddLink(rel, "PUT", href, metadata);
} }
public void AddDeleteLink(string rel, string href) public void AddDeleteLink(string rel, string href, string metadata = null)
{ {
AddLink(rel, "DELETE", href); AddLink(rel, "DELETE", href, metadata);
} }
public void AddLink(string rel, string method, string href) public void AddLink(string rel, string method, string href, string metadata = null)
{ {
Guard.NotNullOrEmpty(rel, nameof(rel)); Guard.NotNullOrEmpty(rel, nameof(rel));
Guard.NotNullOrEmpty(href, nameof(href)); Guard.NotNullOrEmpty(href, nameof(href));
Guard.NotNullOrEmpty(method, nameof(method)); Guard.NotNullOrEmpty(method, nameof(method));
Links[rel] = new ResourceLink { Href = href, Method = method }; Links[rel] = new ResourceLink { Href = href, Method = method, Metadata = metadata };
} }
} }
} }

4
src/Squidex.Web/ResourceLink.cs

@ -18,5 +18,9 @@ namespace Squidex.Web
[Required] [Required]
[Display(Description = "The link method.")] [Display(Description = "The link method.")]
public string Method { get; set; } public string Method { get; set; }
[Required]
[Display(Description = "Additional data about the link.")]
public string Metadata { get; set; }
} }
} }

11
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -71,10 +71,15 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
public Instant LastModified { get; set; } public Instant LastModified { get; set; }
/// <summary> /// <summary>
/// The the status of the content. /// The status of the content.
/// </summary> /// </summary>
public Status Status { get; set; } public Status Status { get; set; }
/// <summary>
/// The color of the status.
/// </summary>
public string StatusColor { get; set; }
/// <summary> /// <summary>
/// The version of the content. /// The version of the content.
/// </summary> /// </summary>
@ -161,9 +166,9 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
foreach (var next in nextStatuses) foreach (var next in nextStatuses)
{ {
if (controller.HasPermission(Helper.StatusPermission(app, schema, next))) if (controller.HasPermission(Helper.StatusPermission(app, schema, next.Status)))
{ {
AddPutLink($"status/{next}", controller.Url<ContentsController>(x => nameof(x.PutContentStatus), values)); AddPutLink($"status/{next.Status}", controller.Url<ContentsController>(x => nameof(x.PutContentStatus), values), next.Color);
} }
} }

4
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -35,7 +35,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// The possible statuses. /// The possible statuses.
/// </summary> /// </summary>
[Required] [Required]
public Status[] Statuses { get; set; } public StatusInfoDto[] Statuses { get; set; }
public string ToEtag() public string ToEtag()
{ {
@ -69,7 +69,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
{ {
var allStatuses = await contentWorkflow.GetAllAsync(schema); var allStatuses = await contentWorkflow.GetAllAsync(schema);
Statuses = allStatuses.ToArray(); Statuses = allStatuses.Select(StatusInfoDto.FromStatusInfo).ToArray();
} }
private async Task AssignContentsAsync(IContentWorkflow contentWorkflow, IResultList<IContentEntity> contents, QueryContext context, ApiController controller) private async Task AssignContentsAsync(IContentWorkflow contentWorkflow, IResultList<IContentEntity> contents, QueryContext context, ApiController controller)

32
src/Squidex/Areas/Api/Controllers/Contents/Models/StatusInfoDto.cs

@ -0,0 +1,32 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Areas.Api.Controllers.Contents.Models
{
public sealed class StatusInfoDto
{
/// <summary>
/// The name of the status.
/// </summary>
[Required]
public Status Status { get; set; }
/// <summary>
/// The color of the status.
/// </summary>
[Required]
public string Color { get; set; }
public static StatusInfoDto FromStatusInfo(StatusInfo statusInfo)
{
return new StatusInfoDto { Status = statusInfo.Status, Color = statusInfo.Color };
}
}
}

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

@ -67,6 +67,9 @@ namespace Squidex.Config.Domain
services.AddTransientAs(c => new RestructureContentCollection(c.GetRequiredService<IMongoClient>().GetDatabase(mongoContentDatabaseName))) services.AddTransientAs(c => new RestructureContentCollection(c.GetRequiredService<IMongoClient>().GetDatabase(mongoContentDatabaseName)))
.As<IMigration>(); .As<IMigration>();
services.AddTransientAs(c => new CreateStatusColors(c.GetRequiredService<IMongoClient>().GetDatabase(mongoContentDatabaseName)))
.As<IMigration>();
services.AddSingletonAs<MongoMigrationStatus>() services.AddSingletonAs<MongoMigrationStatus>()
.As<IMigrationStatus>(); .As<IMigrationStatus>();

2
src/Squidex/app/features/content/pages/contents/contents-filters-page.component.html

@ -17,7 +17,7 @@
<a class="sidebar-item" *ngFor="let query of contentsState.statusQueries | async; trackBy: trackByQuery" (click)="search(query.filter)" <a class="sidebar-item" *ngFor="let query of contentsState.statusQueries | async; trackBy: trackByQuery" (click)="search(query.filter)"
[class.active]="isSelectedQuery(query.filter)"> [class.active]="isSelectedQuery(query.filter)">
{{query.name}} <i class="icon-circle" [style.color]="query.color"></i> {{query.name}}
</a> </a>
</div> </div>

2
src/Squidex/app/features/content/pages/contents/contents-filters-page.component.scss

@ -3,4 +3,4 @@
.text-muted { .text-muted {
pointer-events: none; pointer-events: none;
} }

2
src/Squidex/app/framework/utils/hateos.ts

@ -13,7 +13,7 @@ export interface Resource {
} }
export type ResourceLinks = { [rel: string]: ResourceLink }; export type ResourceLinks = { [rel: string]: ResourceLink };
export type ResourceLink = { href: string; method: ResourceMethod; }; export type ResourceLink = { href: string; method: ResourceMethod; metadata?: string; };
export type Metadata = { [rel: string]: string }; export type Metadata = { [rel: string]: string };

6
src/Squidex/app/shared/services/contents.service.spec.ts

@ -62,11 +62,13 @@ describe('ContentsService', () => {
contentResponse(12), contentResponse(12),
contentResponse(13) contentResponse(13)
], ],
statuses: ['Draft', 'Published'] statuses: [{
status: 'Draft', color: 'Gray'
}]
}); });
expect(contents!).toEqual( expect(contents!).toEqual(
new ContentsDto(['Draft', 'Published'], 10, [ new ContentsDto([{ status: 'Draft', color: 'Gray' }], 10, [
createContent(12), createContent(12),
createContent(13) createContent(13)
])); ]));

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

@ -34,9 +34,11 @@ export class ScheduleDto {
} }
} }
export type StatusInfo = { status: string; color: string; };
export class ContentsDto extends ResultSet<ContentDto> { export class ContentsDto extends ResultSet<ContentDto> {
constructor( constructor(
public readonly statuses: string[], public readonly statuses: StatusInfo[],
total: number, total: number,
items: ContentDto[], items: ContentDto[],
links?: ResourceLinks links?: ResourceLinks
@ -133,7 +135,7 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`);
return this.http.get<{ total: number, items: [], statuses: string[] } & Resource>(url).pipe( return this.http.get<{ total: number, items: [], statuses: StatusInfo[] } & Resource>(url).pipe(
map(({ total, items, statuses, _links }) => { map(({ total, items, statuses, _links }) => {
const contents = items.map(x => parseContent(x)); const contents = items.map(x => parseContent(x));

14
src/Squidex/app/shared/state/contents.state.ts

@ -24,7 +24,7 @@ import { SchemaDto } from './../services/schemas.service';
import { AppsState } from './apps.state'; import { AppsState } from './apps.state';
import { SchemasState } from './schemas.state'; import { SchemasState } from './schemas.state';
import { ContentDto, ContentsService } from './../services/contents.service'; import { ContentDto, ContentsService, StatusInfo } from './../services/contents.service';
interface Snapshot { interface Snapshot {
// The current comments. // The current comments.
@ -40,7 +40,7 @@ interface Snapshot {
isLoaded?: boolean; isLoaded?: boolean;
// The statuses. // The statuses.
statuses?: string[]; statuses?: StatusInfo[];
// The selected content. // The selected content.
selectedContent?: ContentDto | null; selectedContent?: ContentDto | null;
@ -348,10 +348,12 @@ export class ManualContentsState extends ContentsStateBase {
} }
} }
function buildQueries(x: string[] | undefined): { name: string; filter: string; }[] { export type ContentQuery = { color: string; name: string; filter: string; };
return x ? x.map(s => buildQuery(s)) : [];
function buildQueries(statuses: StatusInfo[] | undefined): ContentQuery[] {
return statuses ? statuses.map(s => buildQuery(s)) : [];
} }
function buildQuery(s: string) { function buildQuery(s: StatusInfo) {
return ({ name: s, filter: `$filter=status eq '${s}'` }); return ({ name: s.status, color: s.color, filter: `$filter=status eq '${s}'` });
} }

33
tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs

@ -12,40 +12,49 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
{ {
public class StatusTests public class StatusTests
{ {
[Fact]
public void Should_initialize_default()
{
Status status = default;
Assert.Equal("Unknown", status.Name);
Assert.Equal("Unknown", status.ToString());
}
[Fact] [Fact]
public void Should_initialize_status_from_string() public void Should_initialize_status_from_string()
{ {
var result = new Status("Custom"); var status = new Status("Custom");
Assert.Equal("Custom", result.Name); Assert.Equal("Custom", status.Name);
Assert.Equal("Custom", result.ToString()); Assert.Equal("Custom", status.ToString());
} }
[Fact] [Fact]
public void Should_provide_draft_status() public void Should_provide_draft_status()
{ {
var result = Status.Draft; var status = Status.Draft;
Assert.Equal("Draft", result.Name); Assert.Equal("Draft", status.Name);
Assert.Equal("Draft", result.ToString()); Assert.Equal("Draft", status.ToString());
} }
[Fact] [Fact]
public void Should_provide_archived_status() public void Should_provide_archived_status()
{ {
var result = Status.Archived; var status = Status.Archived;
Assert.Equal("Archived", result.Name); Assert.Equal("Archived", status.Name);
Assert.Equal("Archived", result.ToString()); Assert.Equal("Archived", status.ToString());
} }
[Fact] [Fact]
public void Should_provide_published_status() public void Should_provide_published_status()
{ {
var result = Status.Published; var status = Status.Published;
Assert.Equal("Published", result.Name); Assert.Equal("Published", status.Name);
Assert.Equal("Published", result.ToString()); Assert.Equal("Published", status.ToString());
} }
[Fact] [Fact]

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

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>(); private readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>(); private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
private readonly IContentRepository contentRepository = A.Dummy<IContentRepository>(); private readonly IContentRepository contentRepository = A.Dummy<IContentRepository>();
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(x => x.Wrapping(new DefaultContentWorkflow()));
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAppEntity app = A.Fake<IAppEntity>(); private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE); private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE);
@ -102,12 +102,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored)) A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.ReturnsLazily(x => x.GetArgument<ScriptContext>(0).Data); .ReturnsLazily(x => x.GetArgument<ScriptContext>(0).Data);
A.CallTo(() => contentWorkflow.CanUpdateAsync(A<IContentEntity>.Ignored))
.Returns(true);
A.CallTo(() => contentWorkflow.CanMoveToAsync(A<IContentEntity>.Ignored, A<Status>.Ignored))
.Returns(true);
patched = patch.MergeInto(data); patched = patch.MergeInto(data);
sut = new ContentGrain(Store, A.Dummy<ISemanticLog>(), appProvider, A.Dummy<IAssetRepository>(), scriptEngine, contentWorkflow, contentRepository); sut = new ContentGrain(Store, A.Dummy<ISemanticLog>(), appProvider, A.Dummy<IAssetRepository>(), scriptEngine, contentWorkflow, contentRepository);
@ -132,9 +126,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.ShouldBeEquivalent(sut.Snapshot); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Draft, sut.Snapshot.Status);
Assert.Equal(StatusColors.Draft, sut.Snapshot.StatusColor);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data }) CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft, StatusColor = StatusColors.Draft })
); );
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>")) A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>"))
@ -143,26 +140,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
[Fact]
public async Task Create_should_create_events_and_update_state_with_custom_initial_status()
{
var command = new CreateContent { Data = data };
A.CallTo(() => contentWorkflow.GetInitialStatusAsync(schema))
.Returns(Status.Archived);
var result = await sut.ExecuteAsync(CreateContentCommand(command));
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Archived, sut.Snapshot.Status);
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data, Status = Status.Archived })
);
}
[Fact] [Fact]
public async Task Create_should_also_publish() public async Task Create_should_also_publish()
{ {
@ -172,10 +149,22 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.ShouldBeEquivalent(sut.Snapshot); result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Published, sut.Snapshot.Status);
Assert.Equal(StatusColors.Published, sut.Snapshot.StatusColor);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data }), CreateContentEvent(new ContentCreated
CreateContentEvent(new ContentStatusChanged { Status = Status.Published }) {
Data = data,
Status = Status.Draft,
StatusColor = StatusColors.Draft
}),
CreateContentEvent(new ContentStatusChanged
{
Status = Status.Published,
StatusColor = StatusColors.Published
})
); );
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>")) A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<create-script>"))
@ -246,10 +235,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.ShouldBeEquivalent(sut.Snapshot); result.ShouldBeEquivalent(sut.Snapshot);
LastEvents Assert.Single(LastEvents);
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data })
);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<update-script>")) A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<update-script>"))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -319,10 +305,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.ShouldBeEquivalent(sut.Snapshot); result.ShouldBeEquivalent(sut.Snapshot);
LastEvents Assert.Single(LastEvents);
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data })
);
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<update-script>")) A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, "<update-script>"))
.MustNotHaveHappened(); .MustNotHaveHappened();
@ -343,7 +326,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published }) CreateContentEvent(new ContentStatusChanged
{
Change = StatusChange.Published,
Status = Status.Published,
StatusColor = StatusColors.Published
})
); );
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")) A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
@ -365,7 +353,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) CreateContentEvent(new ContentStatusChanged
{
Status = Status.Archived,
StatusColor = StatusColors.Archived
})
); );
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")) A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
@ -388,7 +380,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) CreateContentEvent(new ContentStatusChanged
{
Change = StatusChange.Unpublished,
Status = Status.Draft,
StatusColor = StatusColors.Draft
})
); );
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")) A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
@ -411,7 +408,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateContentEvent(new ContentStatusChanged { Status = Status.Draft }) CreateContentEvent(new ContentStatusChanged
{
Status = Status.Draft,
StatusColor = StatusColors.Draft
})
); );
A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>")) A.CallTo(() => scriptEngine.Execute(A<ScriptContext>.Ignored, "<change-script>"))
@ -472,11 +473,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task ChangeStatus_should_refresh_properties_and_revert_scheduling_when_invoked_by_scheduler() public async Task ChangeStatus_should_refresh_properties_and_revert_scheduling_when_invoked_by_scheduler()
{ {
await ExecuteCreateAsync(); await ExecuteCreateAsync();
await ExecuteScheduledAsync(); await ExecuteChangeStatusAsync(Status.Published, Instant.MaxValue);
var command = new ChangeContentStatus { Status = Status.Draft, JobId = sut.Snapshot.ScheduleJob.Id }; var command = new ChangeContentStatus { Status = Status.Published, JobId = sut.Snapshot.ScheduleJob.Id };
A.CallTo(() => contentWorkflow.CanMoveToAsync(sut.Snapshot, command.Status)) A.CallTo(() => contentWorkflow.CanMoveToAsync(A<IContentEntity>.Ignored, Status.Published))
.Returns(false); .Returns(false);
var result = await sut.ExecuteAsync(CreateContentCommand(command)); var result = await sut.ExecuteAsync(CreateContentCommand(command));
@ -549,9 +550,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
return sut.ExecuteAsync(CreateContentCommand(new UpdateContent { Data = otherData, AsDraft = true })); return sut.ExecuteAsync(CreateContentCommand(new UpdateContent { Data = otherData, AsDraft = true }));
} }
private Task ExecuteScheduledAsync() private Task ExecuteChangeStatusAsync(Status status, Instant? dueTime = null)
{ {
return sut.ExecuteAsync(CreateContentCommand(new ChangeContentStatus { Status = Status.Published, DueTime = Instant.MaxValue })); return sut.ExecuteAsync(CreateContentCommand(new ChangeContentStatus { Status = status, DueTime = dueTime }));
} }
private Task ExecuteDeleteAsync() private Task ExecuteDeleteAsync()

37
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs

@ -7,6 +7,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Xunit; using Xunit;
@ -19,9 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_draft_as_initial_status() public async Task Should_draft_as_initial_status()
{ {
var expected = new StatusInfo(Status.Draft, StatusColors.Draft);
var result = await sut.GetInitialStatusAsync(null); var result = await sut.GetInitialStatusAsync(null);
Assert.Equal(Status.Draft, result); result.Should().BeEquivalentTo(expected);
} }
[Fact] [Fact]
@ -69,11 +72,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var content = CreateContent(Status.Draft); var content = CreateContent(Status.Draft);
var expected = new[] { Status.Archived, Status.Published }; var expected = new[]
{
new StatusInfo(Status.Archived, StatusColors.Archived),
new StatusInfo(Status.Published, StatusColors.Published)
};
var result = await sut.GetNextsAsync(content); var result = await sut.GetNextsAsync(content);
Assert.Equal(expected, result); result.Should().BeEquivalentTo(expected);
} }
[Fact] [Fact]
@ -81,11 +88,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var content = CreateContent(Status.Archived); var content = CreateContent(Status.Archived);
var expected = new[] { Status.Draft }; var expected = new[]
{
new StatusInfo(Status.Draft, StatusColors.Draft)
};
var result = await sut.GetNextsAsync(content); var result = await sut.GetNextsAsync(content);
Assert.Equal(expected, result); result.Should().BeEquivalentTo(expected);
} }
[Fact] [Fact]
@ -93,21 +103,30 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var content = CreateContent(Status.Published); var content = CreateContent(Status.Published);
var expected = new[] { Status.Draft, Status.Archived }; var expected = new[]
{
new StatusInfo(Status.Archived, StatusColors.Archived),
new StatusInfo(Status.Draft, StatusColors.Draft)
};
var result = await sut.GetNextsAsync(content); var result = await sut.GetNextsAsync(content);
Assert.Equal(expected, result); result.Should().BeEquivalentTo(expected);
} }
[Fact] [Fact]
public async Task Should_return_all_statuses() public async Task Should_return_all_statuses()
{ {
var expected = new[] { Status.Archived, Status.Draft, Status.Published }; var expected = new[]
{
new StatusInfo(Status.Archived, StatusColors.Archived),
new StatusInfo(Status.Draft, StatusColors.Draft),
new StatusInfo(Status.Published, StatusColors.Published)
};
var result = await sut.GetAllAsync(null); var result = await sut.GetAllAsync(null);
Assert.Equal(expected, result); result.Should().BeEquivalentTo(expected);
} }
private IContentEntity CreateContent(Status status) private IContentEntity CreateContent(Status status)

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -262,6 +262,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
lastModified lastModified
lastModifiedBy lastModifiedBy
status status
statusColor
url url
data { data {
myString { myString {
@ -320,6 +321,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
lastModified = content.LastModified, lastModified = content.LastModified,
lastModifiedBy = "subject:user2", lastModifiedBy = "subject:user2",
status = "DRAFT", status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}", url = $"contents/my-schema/{content.Id}",
data = new data = new
{ {
@ -406,6 +408,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
lastModified lastModified
lastModifiedBy lastModifiedBy
status status
statusColor
url url
data { data {
myString { myString {
@ -462,6 +465,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
lastModified = content.LastModified, lastModified = content.LastModified,
lastModifiedBy = "subject:user2", lastModifiedBy = "subject:user2",
status = "DRAFT", status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}", url = $"contents/my-schema/{content.Id}",
data = new data = new
{ {
@ -605,6 +609,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
lastModified lastModified
lastModifiedBy lastModifiedBy
status status
statusColor
url url
data { data {
myString { myString {
@ -653,6 +658,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
lastModified = content.LastModified, lastModified = content.LastModified,
lastModifiedBy = "subject:user2", lastModifiedBy = "subject:user2",
status = "DRAFT", status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}", url = $"contents/my-schema/{content.Id}",
data = new data = new
{ {

3
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -157,7 +157,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"), LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"),
Data = data, Data = data,
DataDraft = dataDraft, DataDraft = dataDraft,
Status = Status.Draft Status = Status.Draft,
StatusColor = "red"
}; };
return content; return content;

8
tools/Migrate_01/MigrationPath.cs

@ -17,7 +17,7 @@ namespace Migrate_01
{ {
public sealed class MigrationPath : IMigrationPath public sealed class MigrationPath : IMigrationPath
{ {
private const int CurrentVersion = 17; private const int CurrentVersion = 18;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public MigrationPath(IServiceProvider serviceProvider) public MigrationPath(IServiceProvider serviceProvider)
@ -115,6 +115,12 @@ namespace Migrate_01
yield return serviceProvider.GetService<RenameSlugField>(); yield return serviceProvider.GetService<RenameSlugField>();
} }
// Version 18: Status colors introduced
if (version < 17)
{
yield return serviceProvider.GetService<CreateStatusColors>();
}
yield return serviceProvider.GetRequiredService<StartEventConsumers>(); yield return serviceProvider.GetRequiredService<StartEventConsumers>();
} }
} }

42
tools/Migrate_01/Migrations/MongoDb/CreateStatusColors.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.Migrations;
namespace Migrate_01.Migrations.MongoDb
{
public sealed class CreateStatusColors : IMigration
{
private readonly IMongoDatabase contentDatabase;
public CreateStatusColors(IMongoDatabase contentDatabase)
{
this.contentDatabase = contentDatabase;
}
public async Task UpdateAsync()
{
var collection = contentDatabase.GetCollection<BsonDocument>("State_Contents");
await collection.UpdateManyAsync(
Builders<BsonDocument>.Filter.Eq("ss", "Archived"),
Builders<BsonDocument>.Update.Set("sc", StatusColors.Archived));
await collection.UpdateManyAsync(
Builders<BsonDocument>.Filter.Eq("ss", "Draft"),
Builders<BsonDocument>.Update.Set("sc", StatusColors.Draft));
await collection.UpdateManyAsync(
Builders<BsonDocument>.Filter.Eq("ss", "Published"),
Builders<BsonDocument>.Update.Set("sc", StatusColors.Published));
}
}
}

7
tools/Migrate_01/OldEvents/AppPlanChangedOld.cs → tools/Migrate_01/OldEvents/AppPlanChanged.cs

@ -5,16 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using AppPlanChangedV2 = Squidex.Domain.Apps.Events.Apps.AppPlanChanged;
namespace Migrate_01.OldEvents namespace Migrate_01.OldEvents
{ {
[TypeName("AppPlanChanged")] [TypeName("AppPlanChanged")]
public sealed class AppPlanChangedOld : AppEvent, IMigrated<IEvent> [Obsolete]
public sealed class AppPlanChanged : AppEvent, IMigrated<IEvent>
{ {
public string PlanId { get; set; } public string PlanId { get; set; }
@ -22,7 +25,7 @@ namespace Migrate_01.OldEvents
{ {
if (!string.IsNullOrWhiteSpace(PlanId)) if (!string.IsNullOrWhiteSpace(PlanId))
{ {
return SimpleMapper.Map(this, new AppPlanChanged()); return SimpleMapper.Map(this, new AppPlanChangedV2());
} }
else else
{ {

51
tools/Migrate_01/OldEvents/ContentCreated.cs

@ -0,0 +1,51 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using ContentCreatedV2 = Squidex.Domain.Apps.Events.Contents.ContentCreated;
namespace Migrate_01.OldEvents
{
[EventType(nameof(ContentCreated))]
[Obsolete]
public sealed class ContentCreated : ContentEvent, IMigrated<IEvent>
{
public Status Status { get; set; }
public NamedContentData Data { get; set; }
public IEvent Migrate()
{
var migrated = SimpleMapper.Map(this, new ContentCreatedV2());
if (migrated.Status == default)
{
migrated.Status = Status.Draft;
}
if (Status == Status.Archived)
{
migrated.StatusColor = StatusColors.Archived;
}
else if (Status == Status.Published)
{
migrated.StatusColor = StatusColors.Published;
}
else
{
migrated.StatusColor = StatusColors.Draft;
}
return this;
}
}
}

60
tools/Migrate_01/OldEvents/ContentStatusChanged.cs

@ -0,0 +1,60 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using ContentStatusChangedV2 = Squidex.Domain.Apps.Events.Contents.ContentStatusChanged;
namespace Migrate_01.OldEvents
{
[EventType(nameof(ContentStatusChanged))]
[Obsolete]
public sealed class ContentStatusChanged : ContentEvent, IMigrated<IEvent>
{
public string Change { get; set; }
public Status Status { get; set; }
public IEvent Migrate()
{
var migrated = SimpleMapper.Map(this, new ContentStatusChangedV2());
if (migrated.Status == default)
{
migrated.Status = Status.Draft;
}
if (Enum.TryParse<StatusChange>(Change, out var result))
{
migrated.Change = result;
}
else
{
migrated.Change = StatusChange.Change;
}
if (Status == Status.Archived)
{
migrated.StatusColor = StatusColors.Archived;
}
else if (Status == Status.Published)
{
migrated.StatusColor = StatusColors.Published;
}
else
{
migrated.StatusColor = StatusColors.Draft;
}
return this;
}
}
}

52
tools/Migrate_01/OldEvents/ContentStatusScheduled.cs

@ -0,0 +1,52 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using ContentStatusScheduledV2 = Squidex.Domain.Apps.Events.Contents.ContentStatusScheduled;
namespace Migrate_01.OldEvents
{
[EventType(nameof(ContentStatusScheduled))]
[Obsolete]
public sealed class ContentStatusScheduled : ContentEvent, IMigrated<IEvent>
{
public Status Status { get; set; }
public Instant DueTime { get; set; }
public IEvent Migrate()
{
var migrated = SimpleMapper.Map(this, new ContentStatusScheduledV2());
if (migrated.Status == default)
{
migrated.Status = Status.Draft;
}
if (Status == Status.Archived)
{
migrated.StatusColor = StatusColors.Archived;
}
else if (Status == Status.Published)
{
migrated.StatusColor = StatusColors.Published;
}
else
{
migrated.StatusColor = StatusColors.Draft;
}
return this;
}
}
}
Loading…
Cancel
Save