Browse Source

Allow scheduling for content update.

pull/285/head
Sebastian 8 years ago
parent
commit
3c2b342730
  1. 28
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  2. 23
      src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs
  3. 22
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  4. 10
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  5. 2
      src/Squidex/app/features/content/shared/content-status.component.html
  6. 4
      src/Squidex/app/framework/angular/forms/date-time-editor.component.html
  7. 2
      src/Squidex/app/framework/angular/modals/tooltip.component.html
  8. 1
      src/Squidex/app/framework/angular/modals/tooltip.component.scss
  9. 3
      src/Squidex/app/framework/angular/modals/tooltip.component.ts
  10. 54
      src/Squidex/app/shared/state/contents.state.ts

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

@ -100,20 +100,24 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
GuardContent.CanChangeContentStatus(Snapshot.IsPending, Snapshot.Status, c);
if (Snapshot.IsPending && Snapshot.Status == Status.Published && c.Status == Status.Published)
if (c.DueTime.HasValue)
{
ConfirmChanges(c);
ScheduleStatus(c);
}
else
{
if (!c.DueTime.HasValue)
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);
ChangeStatus(c);
}
}
});
@ -216,16 +220,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
RaiseEvent(SimpleMapper.Map(command, new ContentUpdateProposed { Data = data }));
}
public void ScheduleStatus(ChangeContentStatus command)
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));
}
public void ChangeStatus(ChangeContentStatus command)
{
if (command.DueTime.HasValue)
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));
}
else
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged()));
}
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged()));
}
private void RaiseEvent(SchemaEvent @event)

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

@ -15,6 +15,7 @@ using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents
@ -24,20 +25,24 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly Lazy<IContentRepository> contentRepository;
private readonly Lazy<ICommandBus> commandBus;
private readonly IClock clock;
private readonly ISemanticLog log;
private TaskScheduler scheduler;
public ContentSchedulerGrain(
Lazy<IContentRepository> contentRepository,
Lazy<ICommandBus> commandBus,
IClock clock)
IClock clock,
ISemanticLog log)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(commandBus, nameof(commandBus));
Guard.NotNull(clock, nameof(clock));
Guard.NotNull(log, nameof(log));
this.contentRepository = contentRepository;
this.commandBus = commandBus;
this.clock = clock;
this.log = log;
}
public override Task OnActivateAsync()
@ -63,11 +68,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
return contentRepository.Value.QueryScheduledWithoutDataAsync(now, content =>
{
return Dispatch(() =>
return Dispatch(async () =>
{
var command = new ChangeContentStatus { ContentId = content.Id, Status = content.ScheduledTo.Value, Actor = content.ScheduledBy };
try
{
var command = new ChangeContentStatus { ContentId = content.Id, Status = content.ScheduledTo.Value, Actor = content.ScheduledBy };
return commandBus.Value.PublishAsync(command);
await commandBus.Value.PublishAsync(command);
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "ChangeStatusScheduled")
.WriteProperty("status", "Failed")
.WriteProperty("contentId", content.Id.ToString()));
}
});
});
}

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

@ -81,13 +81,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
IsPending = false;
}
protected void On(ContentChangesPublished @event)
{
Data = DataDraft;
IsPending = false;
}
protected void On(ContentStatusScheduled @event)
{
ScheduledAt = @event.DueTime;
@ -95,14 +88,25 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
ScheduledTo = @event.Status;
}
protected void On(ContentStatusChanged @event)
protected void On(ContentChangesPublished @event)
{
Status = @event.Status;
ScheduledAt = null;
ScheduledBy = null;
ScheduledTo = null;
Data = DataDraft;
IsPending = false;
}
protected void On(ContentStatusChanged @event)
{
ScheduledAt = null;
ScheduledBy = null;
ScheduledTo = null;
Status = @event.Status;
if (@event.Status == Status.Published)
{
Data = DataDraft;

10
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -174,10 +174,6 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
this.contentForm.loadData(data, this.content && this.content.status === 'Archived');
}
public publishChanges() {
this.contentsState.publishChanges(this.content).onErrorResumeNext().subscribe();
}
public discardChanges() {
this.contentsState.discardChanges(this.content).onErrorResumeNext().subscribe();
}
@ -205,6 +201,12 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
});
}
public publishChanges() {
this.dueTimeSelector.selectDueTime('Publish')
.switchMap(d => this.contentsState.publishChanges(this.content, d)).onErrorResumeNext()
.subscribe();
}
private changeContentItems(action: string, status: string) {
this.dueTimeSelector.selectDueTime(action)
.switchMap(d => this.contentsState.changeStatus(this.content, action, status, d)).onErrorResumeNext()

2
src/Squidex/app/features/content/shared/content-status.component.html

@ -11,7 +11,7 @@
<i class="icon-clock"></i>
</span>
<sqx-tooltip [target]="statusIcon">Will be set to '{{scheduledTo}}' at {{scheduledAt | sqxFullDateTime}}</sqx-tooltip>
<sqx-tooltip position="topRight" [target]="statusIcon">Will be set to '{{scheduledTo}}' at {{scheduledAt | sqxFullDateTime}}</sqx-tooltip>
</span>
<span class="content-status-label" *ngIf="showLabel">{{displayStatus}}</span>

4
src/Squidex/app/framework/angular/forms/date-time-editor.component.html

@ -7,10 +7,10 @@
<input type="text" class="form-control" [formControl]="timeControl" (blur)="touched()" />
</div>
<div class="form-group" *ngIf="showTime">
<button class="btn btn-secondary" (click)="writeNow()">Now</button>
<button class="btn btn-secondary" [disabled]="isDisabled" (click)="writeNow()">Now</button>
</div>
<div class="form-group" *ngIf="!showTime">
<button class="btn btn-secondary" (click)="writeNow()">Today</button>
<button class="btn btn-secondary" [disabled]="isDisabled" (click)="writeNow()">Today</button>
</div>
<div class="form-group" [class.hidden]="!hasValue" *ngIf="!hideClear">
<button class="btn btn-link clear" [disabled]="isDisabled" (click)="reset()">Clear</button>

2
src/Squidex/app/framework/angular/modals/tooltip.component.html

@ -1,3 +1,3 @@
<div class="tooltip-container" *sqxModalView="modal;onRoot:true;closeAuto:false" [sqxModalTarget]="target" position="topLeft">
<div class="tooltip-container" *sqxModalView="modal;onRoot:true;closeAuto:false" [sqxModalTarget]="target" [position]="position">
<ng-content></ng-content>
</div>

1
src/Squidex/app/framework/angular/modals/tooltip.component.scss

@ -7,6 +7,7 @@
border: 0;
font-size: .9rem;
font-weight: normal;
white-space: nowrap;
color: $color-dark-foreground;
padding: .5rem;
}

3
src/Squidex/app/framework/angular/modals/tooltip.component.ts

@ -27,6 +27,9 @@ export class TooltipComponent implements OnDestroy, OnInit {
@Input()
public target: any;
@Input()
public position = 'topLeft';
public modal = new ModalView(false, false);
constructor(

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

@ -288,12 +288,30 @@ export abstract class ContentsStateBase extends State<Snapshot> {
.switchMap(() => this.loadInternal());
}
public publishChanges(content: ContentDto, dueTime: string | null, now?: DateTime): Observable<any> {
return this.contentsService.changeContentStatus(this.appName, this.schemaName, content.id, 'Publish', dueTime, content.version)
.do(dto => {
this.dialogs.notifyInfo('Content updated successfully.');
if (dueTime) {
this.replaceContent(changeScheduleStatus(content, 'Published', dueTime, this.user, dto.version, now));
} else {
this.replaceContent(confirmChanges(content, this.user, dto.version, now));
}
})
.notify(this.dialogs);
}
public changeStatus(content: ContentDto, action: string, status: string, dueTime: string | null, now?: DateTime): Observable<any> {
return this.contentsService.changeContentStatus(this.appName, this.schemaName, content.id, action, dueTime, content.version)
.do(dto => {
this.dialogs.notifyInfo('Content updated successfully.');
this.replaceContent(changeStatus(content, status, dueTime, this.user, dto.version, now));
if (dueTime) {
this.replaceContent(changeScheduleStatus(content, status, dueTime, this.user, dto.version, now));
} else {
this.replaceContent(changeStatus(content, status, this.user, dto.version, now));
}
})
.notify(this.dialogs);
}
@ -318,16 +336,6 @@ export abstract class ContentsStateBase extends State<Snapshot> {
.notify(this.dialogs);
}
public publishChanges(content: ContentDto, now?: DateTime): Observable<any> {
return this.contentsService.changeContentStatus(this.appName, this.schemaName, content.id, 'Publish', null, content.version)
.do(dto => {
this.dialogs.notifyInfo('Content updated successfully.');
this.replaceContent(confirmChanges(content, this.user, dto.version, now));
})
.notify(this.dialogs);
}
public discardChanges(content: ContentDto, now?: DateTime): Observable<any> {
return this.contentsService.discardChanges(this.appName, this.schemaName, content.id, content.version)
.do(dto => {
@ -431,15 +439,29 @@ export class ManualContentsState extends ContentsStateBase {
}
}
const changeStatus = (content: ContentDto, status: string, dueTime: string | null, user: string, version: Version, now?: DateTime) =>
const changeStatus = (content: ContentDto, status: string, user: string, version: Version, now?: DateTime) =>
new ContentDto(
content.id,
status,
content.createdBy, user,
content.created, now || DateTime.now(),
null,
null,
null,
content.isPending,
content.data,
content.dataDraft,
version);
const changeScheduleStatus = (content: ContentDto, status: string, dueTime: string, user: string, version: Version, now?: DateTime) =>
new ContentDto(
content.id,
dueTime ? content.status : status,
content.status,
content.createdBy, user,
content.created, now || DateTime.now(),
dueTime ? status : null,
dueTime ? user : null,
dueTime ? DateTime.parseISO_UTC(dueTime) : null,
status,
user,
DateTime.parseISO_UTC(dueTime),
content.isPending,
content.data,
content.dataDraft,

Loading…
Cancel
Save