Browse Source

Allow scheduling for content update.

pull/285/head
Sebastian 9 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); 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 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."); 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); 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 })); 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) public void ChangeStatus(ChangeContentStatus command)
{ {
if (command.DueTime.HasValue) RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged()));
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value }));
}
else
{
RaiseEvent(SimpleMapper.Map(command, new ContentStatusChanged()));
}
} }
private void RaiseEvent(SchemaEvent @event) 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.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
@ -24,20 +25,24 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly Lazy<IContentRepository> contentRepository; private readonly Lazy<IContentRepository> contentRepository;
private readonly Lazy<ICommandBus> commandBus; private readonly Lazy<ICommandBus> commandBus;
private readonly IClock clock; private readonly IClock clock;
private readonly ISemanticLog log;
private TaskScheduler scheduler; private TaskScheduler scheduler;
public ContentSchedulerGrain( public ContentSchedulerGrain(
Lazy<IContentRepository> contentRepository, Lazy<IContentRepository> contentRepository,
Lazy<ICommandBus> commandBus, Lazy<ICommandBus> commandBus,
IClock clock) IClock clock,
ISemanticLog log)
{ {
Guard.NotNull(contentRepository, nameof(contentRepository)); Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(commandBus, nameof(commandBus)); Guard.NotNull(commandBus, nameof(commandBus));
Guard.NotNull(clock, nameof(clock)); Guard.NotNull(clock, nameof(clock));
Guard.NotNull(log, nameof(log));
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
this.commandBus = commandBus; this.commandBus = commandBus;
this.clock = clock; this.clock = clock;
this.log = log;
} }
public override Task OnActivateAsync() public override Task OnActivateAsync()
@ -63,11 +68,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
return contentRepository.Value.QueryScheduledWithoutDataAsync(now, content => 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; IsPending = false;
} }
protected void On(ContentChangesPublished @event)
{
Data = DataDraft;
IsPending = false;
}
protected void On(ContentStatusScheduled @event) protected void On(ContentStatusScheduled @event)
{ {
ScheduledAt = @event.DueTime; ScheduledAt = @event.DueTime;
@ -95,14 +88,25 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
ScheduledTo = @event.Status; 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; ScheduledAt = null;
ScheduledBy = null; ScheduledBy = null;
ScheduledTo = null; ScheduledTo = null;
Status = @event.Status;
if (@event.Status == Status.Published) if (@event.Status == Status.Published)
{ {
Data = DataDraft; 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'); this.contentForm.loadData(data, this.content && this.content.status === 'Archived');
} }
public publishChanges() {
this.contentsState.publishChanges(this.content).onErrorResumeNext().subscribe();
}
public discardChanges() { public discardChanges() {
this.contentsState.discardChanges(this.content).onErrorResumeNext().subscribe(); 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) { private changeContentItems(action: string, status: string) {
this.dueTimeSelector.selectDueTime(action) this.dueTimeSelector.selectDueTime(action)
.switchMap(d => this.contentsState.changeStatus(this.content, action, status, d)).onErrorResumeNext() .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> <i class="icon-clock"></i>
</span> </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>
<span class="content-status-label" *ngIf="showLabel">{{displayStatus}}</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()" /> <input type="text" class="form-control" [formControl]="timeControl" (blur)="touched()" />
</div> </div>
<div class="form-group" *ngIf="showTime"> <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>
<div class="form-group" *ngIf="!showTime"> <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>
<div class="form-group" [class.hidden]="!hasValue" *ngIf="!hideClear"> <div class="form-group" [class.hidden]="!hasValue" *ngIf="!hideClear">
<button class="btn btn-link clear" [disabled]="isDisabled" (click)="reset()">Clear</button> <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> <ng-content></ng-content>
</div> </div>

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

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

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

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

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

@ -288,12 +288,30 @@ export abstract class ContentsStateBase extends State<Snapshot> {
.switchMap(() => this.loadInternal()); .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> { 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) return this.contentsService.changeContentStatus(this.appName, this.schemaName, content.id, action, dueTime, content.version)
.do(dto => { .do(dto => {
this.dialogs.notifyInfo('Content updated successfully.'); 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); .notify(this.dialogs);
} }
@ -318,16 +336,6 @@ export abstract class ContentsStateBase extends State<Snapshot> {
.notify(this.dialogs); .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> { public discardChanges(content: ContentDto, now?: DateTime): Observable<any> {
return this.contentsService.discardChanges(this.appName, this.schemaName, content.id, content.version) return this.contentsService.discardChanges(this.appName, this.schemaName, content.id, content.version)
.do(dto => { .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( new ContentDto(
content.id, content.id,
dueTime ? content.status : status, content.status,
content.createdBy, user, content.createdBy, user,
content.created, now || DateTime.now(), content.created, now || DateTime.now(),
dueTime ? status : null, status,
dueTime ? user : null, user,
dueTime ? DateTime.parseISO_UTC(dueTime) : null, DateTime.parseISO_UTC(dueTime),
content.isPending, content.isPending,
content.data, content.data,
content.dataDraft, content.dataDraft,

Loading…
Cancel
Save