Browse Source

Handle scheduling failures.

pull/285/head
Sebastian 8 years ago
parent
commit
8c067ca66b
  1. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  2. 1
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  3. 3
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs
  4. 6
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  5. 43
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  6. 3
      src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs
  7. 9
      src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs
  8. 7
      src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs
  9. 33
      src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs
  10. 34
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  11. 16
      src/Squidex.Domain.Apps.Events/Contents/ContentSchedulingCancelled.cs
  12. 0
      src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs
  13. 6
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  14. 0
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs
  15. 0
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs
  16. 29
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  17. 0
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  18. 37
      src/Squidex/Areas/Api/Controllers/Contents/Models/ScheduleJobDto.cs
  19. 4
      src/Squidex/app/features/content/pages/content/content-page.component.html
  20. 4
      src/Squidex/app/features/content/shared/content-item.component.html
  21. 29
      src/Squidex/app/shared/services/contents.service.spec.ts
  22. 36
      src/Squidex/app/shared/services/contents.service.ts
  23. 24
      src/Squidex/app/shared/state/contents.state.ts
  24. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs

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

@ -63,6 +63,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonJson]
public IdContentData DataDraftByIds { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sj")]
[BsonJson]
public ScheduleJob ScheduleJob { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("dt")]
public string DataText { get; set; }
@ -75,18 +80,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonElement("si")]
public NamedId<Guid> SchemaId { get; set; }
[BsonIgnoreIfNull]
[BsonElement("st")]
public Status? ScheduledTo { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sa")]
public Instant? ScheduledAt { get; set; }
[BsonIgnoreIfNull]
[BsonElement("sb")]
public RefToken ScheduledBy { get; set; }
[BsonRequired]
[BsonElement("ct")]
public Instant Created { get; set; }

1
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -43,6 +43,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
IndexedAppId = value.AppId.Id,
IndexedSchemaId = value.SchemaId.Id,
ReferencedIds = idData.ToReferencedIds(schema.SchemaDef),
ScheduledAt = value.ScheduleJob?.DueTime,
Version = newVersion
});

3
src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// =========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
@ -15,5 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public Status Status { get; set; }
public Instant? DueTime { get; set; }
public Guid? JobId { get; set; }
}
}

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

@ -30,11 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public Status Status { get; set; }
public Status? ScheduledTo { get; set; }
public Instant? ScheduledAt { get; set; }
public RefToken ScheduledBy { get; set; }
public ScheduleJob ScheduleJob { get; set; }
public RefToken CreatedBy { get; set; }

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

@ -98,25 +98,39 @@ namespace Squidex.Domain.Apps.Entities.Contents
case ChangeContentStatus changeContentStatus:
return UpdateAsync(changeContentStatus, async c =>
{
GuardContent.CanChangeContentStatus(Snapshot.IsPending, Snapshot.Status, c);
if (c.DueTime.HasValue)
try
{
ScheduleStatus(c);
GuardContent.CanChangeContentStatus(Snapshot.IsPending, Snapshot.Status, c);
if (c.DueTime.HasValue)
{
ScheduleStatus(c);
}
else
{
if (Snapshot.IsPending && Snapshot.Status == Status.Published && c.Status == Status.Published)
{
ConfirmChanges(c);
}
else
{
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to change content.");
await ctx.ExecuteScriptAsync(x => x.ScriptChange, c.Status, c, Snapshot.Data);
ChangeStatus(c);
}
}
}
else
catch (Exception)
{
if (Snapshot.IsPending && Snapshot.Status == Status.Published && c.Status == Status.Published)
if (c.JobId.HasValue && Snapshot?.ScheduleJob.Id == c.JobId)
{
ConfirmChanges(c);
CancelScheduling(c);
}
else
{
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to change content.");
await ctx.ExecuteScriptAsync(x => x.ScriptChange, c.Status, c, Snapshot.Data);
ChangeStatus(c);
throw;
}
}
});
@ -220,6 +234,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
RaiseEvent(SimpleMapper.Map(command, new ContentUpdateProposed { Data = data }));
}
public void CancelScheduling(ChangeContentStatus command)
{
RaiseEvent(SimpleMapper.Map(command, new ContentSchedulingCancelled()));
}
public void ScheduleStatus(ChangeContentStatus command)
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));

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

@ -37,6 +37,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
AddEventMessage<ContentUpdateProposed>(
"proposed update for {[Schema]} content.");
AddEventMessage<ContentSchedulingCancelled>(
"failed to schedule status change for {[Schema]} content.");
AddEventMessage<ContentStatusChanged>(
"changed status of {[Schema]} content to {[Status]}.");

9
src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs

@ -72,9 +72,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
try
{
var command = new ChangeContentStatus { ContentId = content.Id, Status = content.ScheduledTo.Value, Actor = content.ScheduledBy };
var job = content.ScheduleJob;
await commandBus.Value.PublishAsync(command);
if (job != null)
{
var command = new ChangeContentStatus { ContentId = content.Id, Status = job.Status, Actor = job.ScheduledBy, JobId = job.Id };
await commandBus.Value.PublishAsync(command);
}
}
catch (Exception ex)
{

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

@ -7,7 +7,6 @@
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
@ -25,11 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Status Status { get; }
Status? ScheduledTo { get; }
Instant? ScheduledAt { get; }
RefToken ScheduledBy { get; }
ScheduleJob ScheduleJob { get; }
NamedContentData Data { get; }

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

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ScheduleJob
{
public Guid Id { get; }
public Status Status { get; }
public RefToken ScheduledBy { get; }
public Instant DueTime { get; }
public ScheduleJob(Guid id, Status status, RefToken scheduledBy, Instant dueTime)
{
Id = id;
ScheduledBy = scheduledBy;
Status = status;
DueTime = dueTime;
}
}
}

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

@ -7,7 +7,6 @@
using System;
using Newtonsoft.Json;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
@ -36,13 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
public Status Status { get; set; }
[JsonProperty]
public Status? ScheduledTo { get; set; }
[JsonProperty]
public Instant? ScheduledAt { get; set; }
[JsonProperty]
public RefToken ScheduledBy { get; set; }
public ScheduleJob ScheduleJob { get; set; }
[JsonProperty]
public bool IsPending { get; set; }
@ -81,18 +74,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
IsPending = false;
}
protected void On(ContentStatusScheduled @event)
{
ScheduledAt = @event.DueTime;
ScheduledBy = @event.Actor;
ScheduledTo = @event.Status;
}
protected void On(ContentChangesPublished @event)
{
ScheduledAt = null;
ScheduledBy = null;
ScheduledTo = null;
ScheduleJob = null;
Data = DataDraft;
@ -101,9 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
protected void On(ContentStatusChanged @event)
{
ScheduledAt = null;
ScheduledBy = null;
ScheduledTo = null;
ScheduleJob = null;
Status = @event.Status;
@ -113,6 +95,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
}
}
protected void On(ContentSchedulingCancelled @event)
{
ScheduleJob = null;
}
protected void On(ContentStatusScheduled @event)
{
ScheduleJob = new ScheduleJob(Guid.NewGuid(), @event.Status, @event.Actor, @event.DueTime);
}
protected void On(ContentDeleted @event)
{
IsDeleted = true;

16
src/Squidex.Domain.Apps.Events/Contents/ContentSchedulingCancelled.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Contents
{
[EventType(nameof(ContentSchedulingCancelled))]
public sealed class ContentSchedulingCancelled : ContentEvent
{
}
}

0
src/Squidex/Areas/Api/Controllers/Content/ContentSwaggerController.cs → src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs

6
src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs → src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -119,7 +119,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var response = new ContentsDto
{
Total = result.Total,
Items = result.Take(200).Select(item => SimpleMapper.Map(item, new ContentDto { Data = item.Data, DataDraft = item.DataDraft })).ToArray()
Items = result.Take(200).Select(ContentDto.FromContent).ToArray()
};
Response.Headers["Surrogate-Key"] = string.Join(" ", response.Items.Select(x => x.Id));
@ -148,7 +148,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{
var content = await contentQuery.FindContentAsync(App, name, User, id);
var response = SimpleMapper.Map(content, new ContentDto { Data = content.Data, DataDraft = content.DataDraft });
var response = ContentDto.FromContent(content);
Response.Headers["ETag"] = content.Version.ToString();
Response.Headers["Surrogate-Key"] = content.Id.ToString();
@ -179,7 +179,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{
var content = await contentQuery.FindContentAsync(App, name, User, id, version);
var response = SimpleMapper.Map(content, new ContentDto { Data = content.Data, DataDraft = content.DataDraft });
var response = ContentDto.FromContent(content);
Response.Headers["ETag"] = content.Version.ToString();
Response.Headers["Surrogate-Key"] = content.Id.ToString();

0
src/Squidex/Areas/Api/Controllers/Content/Generator/SchemaSwaggerGenerator.cs → src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs

0
src/Squidex/Areas/Api/Controllers/Content/Generator/SchemasSwaggerGenerator.cs → src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs

29
src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs → src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -9,9 +9,11 @@ using System;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Contents.Models
{
@ -53,17 +55,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// <summary>
/// The scheduled status.
/// </summary>
public Status? ScheduledTo { get; set; }
/// <summary>
/// The scheduled date.
/// </summary>
public Instant? ScheduledAt { get; set; }
/// <summary>
/// The user that has scheduled the content.
/// </summary>
public RefToken ScheduledBy { get; set; }
public ScheduleJobDto ScheduleJob { get; set; }
/// <summary>
/// The date and time when the content item has been created.
@ -103,5 +95,20 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
return response;
}
public static ContentDto FromContent(IContentEntity content)
{
var response = SimpleMapper.Map(content, new ContentDto());
response.Data = content.Data;
response.DataDraft = content.DataDraft;
if (content.ScheduleJob != null)
{
response.ScheduleJob = SimpleMapper.Map(content.ScheduleJob, new ScheduleJobDto());
}
return response;
}
}
}

0
src/Squidex/Areas/Api/Controllers/Content/Models/ContentsDto.cs → src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

37
src/Squidex/Areas/Api/Controllers/Contents/Models/ScheduleJobDto.cs

@ -0,0 +1,37 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Areas.Api.Controllers.Contents.Models
{
public sealed class ScheduleJobDto
{
/// <summary>
/// The id of the schedule job.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The new status.
/// </summary>
public Status Status { get; set; }
/// <summary>
/// The user who schedule the content.
/// </summary>
public RefToken ScheduledBy { get; set; }
/// <summary>
/// The target date and time when the content should be scheduled.
/// </summary>
public Instant DueTime { get; set; }
}
}

4
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -35,8 +35,8 @@
<button type="button" class="btn btn-outline-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #optionsButton>
<sqx-content-status
[status]="content.status"
[scheduledTo]="content.scheduledTo"
[scheduledAt]="content.scheduledAt"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending"
showLabel="true">
</sqx-content-status>

4
src/Squidex/app/features/content/shared/content-item.component.html

@ -58,8 +58,8 @@
<td class="cell-time" (click)="shouldStop($event)">
<sqx-content-status
[status]="content.status"
[scheduledTo]="content.scheduledTo"
[scheduledAt]="content.scheduledAt"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"
[isPending]="content.isPending">
</sqx-content-status>

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

@ -15,6 +15,7 @@ import {
ContentsDto,
ContentsService,
DateTime,
ScheduleDto,
Version
} from './../';
@ -62,9 +63,11 @@ describe('ContentsService', () => {
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
scheduledTo: 'Draft',
scheduledBy: 'Scheduler1',
scheduledAt: '2018-12-12T10:10',
scheduleJob: {
status: 'Draft',
scheduledBy: 'Scheduler1',
when: '2018-12-12T10:10'
},
isPending: true,
version: 11,
data: {},
@ -89,9 +92,7 @@ describe('ContentsService', () => {
new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'Draft',
'Scheduler1',
DateTime.parseISO_UTC('2018-12-12T10:10'),
new ScheduleDto('Draft', 'Scheduler1', DateTime.parseISO_UTC('2018-12-12T10:10')),
true,
{},
{},
@ -100,8 +101,6 @@ describe('ContentsService', () => {
DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'),
null,
null,
null,
false,
{},
{},
@ -169,9 +168,11 @@ describe('ContentsService', () => {
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
scheduledTo: 'Draft',
scheduledBy: 'Scheduler1',
scheduledAt: '2018-12-12T10:10',
scheduleJob: {
status: 'Draft',
scheduledBy: 'Scheduler1',
when: '2018-12-12T10:10'
},
isPending: true,
data: {},
dataDraft: {}
@ -185,9 +186,7 @@ describe('ContentsService', () => {
new ContentDto('id1', 'Published', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'Draft',
'Scheduler1',
DateTime.parseISO_UTC('2018-12-12T10:10'),
new ScheduleDto('Draft', 'Scheduler1', DateTime.parseISO_UTC('2018-12-12T10:10')),
true,
{},
{},
@ -229,8 +228,6 @@ describe('ContentsService', () => {
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
null,
null,
null,
true,
null,
{},

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

@ -28,17 +28,25 @@ export class ContentsDto {
}
}
export class ScheduleDto {
constructor(
public readonly status: string,
public readonly scheduledBy: string,
public readonly when: DateTime
) {
}
}
export class ContentDto {
constructor(
public readonly id: string,
public readonly id: string,
public readonly status: string,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly scheduledTo: string | null,
public readonly scheduledBy: string | null,
public readonly scheduledAt: DateTime | null,
public readonly scheduleJob: ScheduleDto | null,
public readonly isPending: boolean,
public readonly data: object | any,
public readonly dataDraft: object,
@ -103,9 +111,12 @@ export class ContentsService {
item.lastModifiedBy,
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified),
item.scheduledTo || null,
item.scheduledBy || null,
item.scheduledAt ? DateTime.parseISO_UTC(item.scheduledAt) : null,
item.scheduleJob
? new ScheduleDto(
item.scheduleJob.status,
item.scheduleJob.scheduledBy,
DateTime.parseISO_UTC(item.scheduleJob.when))
: null,
item.isPending === true,
item.data,
item.dataDraft,
@ -129,9 +140,12 @@ export class ContentsService {
body.lastModifiedBy,
DateTime.parseISO_UTC(body.created),
DateTime.parseISO_UTC(body.lastModified),
body.scheduledTo || null,
body.scheduledBy || null,
body.scheduledAt || null ? DateTime.parseISO_UTC(body.scheduledAt) : null,
body.scheduleJob
? new ScheduleDto(
body.scheduleJob.status,
body.scheduleJob.scheduledBy,
DateTime.parseISO_UTC(body.scheduleJob.when))
: null,
body.isPending === true,
body.data,
body.dataDraft,
@ -165,8 +179,6 @@ export class ContentsService {
DateTime.parseISO_UTC(body.created),
DateTime.parseISO_UTC(body.lastModified),
null,
null,
null,
true,
null,
body.data,

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

@ -29,7 +29,7 @@ import { fieldInvariant, SchemaDetailsDto, SchemaDto } from './../services/schem
import { AppsState } from './apps.state';
import { SchemasState } from './schemas.state';
import { ContentDto, ContentsService } from './../services/contents.service';
import { ContentDto, ContentsService, ScheduleDto } from './../services/contents.service';
export class EditContentForm extends Form<FormGroup> {
constructor(
@ -446,8 +446,6 @@ const changeStatus = (content: ContentDto, status: string, user: string, version
content.createdBy, user,
content.created, now || DateTime.now(),
null,
null,
null,
content.isPending,
content.data,
content.dataDraft,
@ -459,9 +457,7 @@ const changeScheduleStatus = (content: ContentDto, status: string, dueTime: stri
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
status,
user,
DateTime.parseISO_UTC(dueTime),
new ScheduleDto(status, user, DateTime.parseISO_UTC(dueTime)),
content.isPending,
content.data,
content.dataDraft,
@ -473,9 +469,7 @@ const updateData = (content: ContentDto, data: any, user: string, version: Versi
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
content.scheduledTo,
content.scheduledBy,
content.scheduledAt,
content.scheduleJob,
content.isPending,
data,
data,
@ -487,9 +481,7 @@ const updateDataDraft = (content: ContentDto, data: any, user: string, version:
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
content.scheduledTo,
content.scheduledBy,
content.scheduledAt,
content.scheduleJob,
true,
content.data,
data,
@ -501,9 +493,7 @@ const confirmChanges = (content: ContentDto, user: string, version: Version, now
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
content.scheduledTo,
content.scheduledBy,
content.scheduledAt,
null,
false,
content.dataDraft,
content.dataDraft,
@ -515,9 +505,7 @@ const discardChanges = (content: ContentDto, user: string, version: Version, now
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
content.scheduledTo,
content.scheduledBy,
content.scheduledAt,
content.scheduleJob,
false,
content.data,
content.data,

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

@ -357,8 +357,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal(Status.Draft, sut.Snapshot.Status);
Assert.Equal(Status.Published, sut.Snapshot.ScheduledTo);
Assert.Equal(dueTime, sut.Snapshot.ScheduledAt);
Assert.Equal(Status.Published, sut.Snapshot.ScheduleJob.Status);
Assert.Equal(dueTime, sut.Snapshot.ScheduleJob.DueTime);
LastEvents
.ShouldHaveSameEvents(
@ -369,6 +369,28 @@ namespace Squidex.Domain.Apps.Entities.Contents
.MustNotHaveHappened();
}
[Fact]
public async Task ChangeStatus_should_refresh_properties_and_revert_scheduling_when_invoked_by_scheduler()
{
var dueTime = Instant.MaxValue;
await ExecuteCreateAsync();
await ExecuteScheduledAsync();
var command = new ChangeContentStatus { Status = Status.Draft, JobId = sut.Snapshot.ScheduleJob.Id };
var result = await sut.ExecuteAsync(CreateContentCommand(command));
result.ShouldBeEquivalent(new EntitySavedResult(2));
Assert.Null(sut.Snapshot.ScheduleJob);
LastEvents
.ShouldHaveSameEvents(
CreateContentEvent(new ContentSchedulingCancelled())
);
}
[Fact]
public async Task Delete_should_update_properties_and_create_events()
{
@ -427,6 +449,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
return sut.ExecuteAsync(CreateContentCommand(new UpdateContent { Data = otherData, AsDraft = true }));
}
private Task ExecuteScheduledAsync()
{
return sut.ExecuteAsync(CreateContentCommand(new ChangeContentStatus { Status = Status.Published, DueTime = Instant.MaxValue }));
}
private Task ExecuteDeleteAsync()
{
return sut.ExecuteAsync(CreateContentCommand(new DeleteContent()));

Loading…
Cancel
Save