Browse Source

Fixes for performance improvement.

pull/382/head
Sebastian Stehle 7 years ago
parent
commit
2ea39cf7a5
  1. 22
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  2. 33
      src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  3. 4
      src/Squidex.Web/Json/TypedJsonInheritanceConverter.cs
  4. 4
      src/Squidex.Web/Pipeline/DeferredActionFilter.cs
  5. 4
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  6. 2
      src/Squidex/app/features/settings/pages/workflows/workflow-step.component.html
  7. 6
      src/Squidex/app/framework/angular/modals/modal-view.directive.ts
  8. 2
      src/Squidex/app/shared/services/contents.service.ts
  9. 10
      src/Squidex/app/shared/services/workflows.service.spec.ts
  10. 92
      src/Squidex/app/shared/services/workflows.service.ts
  11. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  12. 5
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  13. 24
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

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

@ -93,17 +93,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
case UpdateContent updateContent:
return UpdateReturnAsync(updateContent, async c =>
{
await GuardContent.CanUpdate(Snapshot, contentWorkflow, c);
var isProposal = c.AsDraft && Snapshot.Status == Status.Published;
return await UpdateAsync(c, x => c.Data, false);
await GuardContent.CanUpdate(Snapshot, contentWorkflow, c, isProposal);
return await UpdateAsync(c, x => c.Data, false, isProposal);
});
case PatchContent patchContent:
return UpdateReturnAsync(patchContent, async c =>
{
await GuardContent.CanPatch(Snapshot, contentWorkflow, c);
var isProposal = c.AsDraft && Snapshot.Status == Status.Published;
await GuardContent.CanPatch(Snapshot, contentWorkflow, c, isProposal);
return await UpdateAsync(c, c.Data.MergeInto, true);
return await UpdateAsync(c, c.Data.MergeInto, true, isProposal);
});
case ChangeContentStatus changeContentStatus:
@ -111,9 +115,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
try
{
var isChangeConfirm = Snapshot.IsPending && Snapshot.Status == Status.Published && c.Status == Status.Published;
var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to change content.");
await GuardContent.CanChangeStatus(ctx.Schema, Snapshot, contentWorkflow, c);
await GuardContent.CanChangeStatus(ctx.Schema, Snapshot, contentWorkflow, c, isChangeConfirm);
if (c.DueTime.HasValue)
{
@ -121,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
else
{
if (Snapshot.IsPending && Snapshot.Status == Status.Published && c.Status == Status.Published)
if (isChangeConfirm)
{
ConfirmChanges(c);
}
@ -190,10 +196,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
}
private async Task<object> UpdateAsync(ContentUpdateCommand command, Func<NamedContentData, NamedContentData> newDataFunc, bool partial)
private async Task<object> UpdateAsync(ContentUpdateCommand command, Func<NamedContentData, NamedContentData> newDataFunc, bool partial, bool isProposal)
{
var isProposal = command.AsDraft && Snapshot.Status == Status.Published;
var currentData =
isProposal ?
Snapshot.DataDraft :

33
src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
}
}
public static async Task CanUpdate(IContentEntity content, IContentWorkflow contentWorkflow, UpdateContent command)
public static async Task CanUpdate(IContentEntity content, IContentWorkflow contentWorkflow, UpdateContent command, bool isProposal)
{
Guard.NotNull(command, nameof(command));
@ -45,10 +45,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
ValidateData(command, e);
});
await ValidateCanUpdate(content, contentWorkflow);
if (!isProposal)
{
await ValidateCanUpdate(content, contentWorkflow);
}
}
public static async Task CanPatch(IContentEntity content, IContentWorkflow contentWorkflow, PatchContent command)
public static async Task CanPatch(IContentEntity content, IContentWorkflow contentWorkflow, PatchContent command, bool isProposal)
{
Guard.NotNull(command, nameof(command));
@ -57,7 +60,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
ValidateData(command, e);
});
await ValidateCanUpdate(content, contentWorkflow);
if (!isProposal)
{
await ValidateCanUpdate(content, contentWorkflow);
}
}
public static void CanDiscardChanges(bool isPending, DiscardChanges command)
@ -70,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
}
}
public static Task CanChangeStatus(ISchemaEntity schema, IContentEntity content, IContentWorkflow contentWorkflow, ChangeContentStatus command)
public static Task CanChangeStatus(ISchemaEntity schema, IContentEntity content, IContentWorkflow contentWorkflow, ChangeContentStatus command, bool isChangeConfirm)
{
Guard.NotNull(command, nameof(command));
@ -81,20 +87,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
return Validate.It(() => "Cannot change status.", async e =>
{
if (!await contentWorkflow.CanMoveToAsync(content, command.Status, command.User))
if (isChangeConfirm)
{
if (content.Status == command.Status && content.Status == Status.Published)
if (!content.IsPending)
{
if (!content.IsPending)
{
e("Content has no changes to publish.", nameof(command.Status));
}
}
else
{
e($"Cannot change status from {content.Status} to {command.Status}.", nameof(command.Status));
e("Content has no changes to publish.", nameof(command.Status));
}
}
else if (!await contentWorkflow.CanMoveToAsync(content, command.Status, command.User))
{
e($"Cannot change status from {content.Status} to {command.Status}.", nameof(command.Status));
}
if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant())
{

4
src/Squidex.Web/Json/TypedJsonInheritanceConverter.cs

@ -79,9 +79,7 @@ namespace Squidex.Web.Json
public TypedJsonInheritanceConverter(string discriminator, IReadOnlyDictionary<string, Type> mapping)
: base(typeof(T), discriminator)
{
Guard.NotNull(maping, nameof(maping));
maping = mapping;
maping = mapping ?? DefaultMapping.Value;
}
protected override Type GetDiscriminatorType(JObject jObject, Type objectType, string discriminatorValue)

4
src/Squidex.Web/Pipeline/DeferredActionFilter.cs

@ -15,9 +15,9 @@ namespace Squidex.Web.Pipeline
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await next();
var resultContext = await next();
if (context.Result is ObjectResult objectResult && objectResult.Value is Deferred deferred)
if (resultContext.Result is ObjectResult objectResult && objectResult.Value is Deferred deferred)
{
objectResult.Value = await deferred.Value;
}

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

@ -149,7 +149,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
this.contentForm.submitFailed(error);
});
} else {
if (this.content && !this.content.canUpdate) {
if (this.content && !this.content.canUpdateAny) {
return;
}
@ -183,7 +183,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
private loadContent(data: any) {
this.contentForm.loadContent(data);
this.contentForm.setEnabled(!this.content || this.content.canUpdate);
this.contentForm.setEnabled(!this.content || this.content.canUpdateAny);
}
public discardChanges() {

2
src/Squidex/app/features/settings/pages/workflows/workflow-step.component.html

@ -13,7 +13,7 @@
[ngModelOptions]="onBlur"
[ngModel]="step.color"
(ngModelChange)="changeColor($event)"
[disabled]="step.isLocked || disabled">
[disabled]="disabled">
</sqx-color-picker>
</div>
<div class="col">

6
src/Squidex/app/framework/angular/modals/modal-view.directive.ts

@ -121,7 +121,11 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
if (this.closeAlways) {
this.modalView.hide();
const modal = this.modalView;
setTimeout(() => {
modal.hide();
}, 100);
} else {
try {
const rootNode = this.renderedView.rootNodes[0];

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

@ -65,6 +65,7 @@ export class ContentDto {
public readonly canDraftPropose: boolean;
public readonly canDraftPublish: boolean;
public readonly canUpdate: boolean;
public readonly canUpdateAny: boolean;
constructor(links: ResourceLinks,
public readonly id: string,
@ -87,6 +88,7 @@ export class ContentDto {
this.canDraftPropose = hasAnyLink(links, 'draft/propose');
this.canDraftPublish = hasAnyLink(links, 'draft/publish');
this.canUpdate = hasAnyLink(links, 'update');
this.canUpdateAny = this.canUpdate || this.canDraftPropose;
this.statusUpdates = Object.keys(links).filter(x => x.startsWith('status/')).map(x => ({ status: x.substr(7), color: links[x].metadata! }));
}

10
src/Squidex/app/shared/services/workflows.service.spec.ts

@ -260,16 +260,6 @@ describe('Workflow', () => {
});
});
it('should return same workflow if step to update is locked', () => {
const workflow =
new WorkflowDto({}, 'id')
.setStep('1', { color: '#00ff00', isLocked: true });
const updated = workflow.setStep('1', { color: 'red' });
expect(updated).toBe(workflow);
});
it('should sort steps case invariant', () => {
const workflow =
new WorkflowDto({}, 'id')

92
src/Squidex/app/shared/services/workflows.service.ts

@ -43,17 +43,6 @@ export class WorkflowDto extends Model<WorkflowDto> {
public readonly displayName: string;
public static DEFAULT =
new WorkflowDto({}, 'id', 'name')
.setStep('Draft', { color: '#8091a5' })
.setStep('Archived', { color: '#eb3142', noUpdate: true })
.setStep('Published', { color: '#4bb958', isLocked: true })
.setTransition('Archived', 'Draft')
.setTransition('Draft', 'Archived')
.setTransition('Draft', 'Published')
.setTransition('Published', 'Draft')
.setTransition('Published', 'Archived');
constructor(
links: ResourceLinks = {},
public readonly id: string,
@ -94,27 +83,29 @@ export class WorkflowDto extends Model<WorkflowDto> {
}
public setStep(name: string, values: Partial<WorkflowStepValues> = {}) {
const found = this.getStep(name);
if (found) {
const { name: _, ...existing } = found;
const old = this.getStep(name);
if (found.isLocked) {
return this;
}
const step = { ...old, name, ...values };
const steps = [...this.steps.filter(s => s !== old), step];
values = { ...existing, ...values };
if (steps.length === 1) {
return this.with({ initial: name, steps });
} else {
return this.with({ steps });
}
}
const steps = [...this.steps.filter(s => s !== found), { name, ...values }];
public setTransition(from: string, to: string, values: Partial<WorkflowTransitionValues> = {}) {
if (!this.getStep(from) || !this.getStep(to)) {
return this;
}
let initial = this.initial;
const old = this.transitions.find(x => x.from === from && x.to === to);
if (steps.length === 1) {
initial = steps[0].name;
}
const transition = { ...old, from, to, ...values };
const transitions = [...this.transitions.filter(t => t !== old), transition];
return this.with({ initial, steps });
return this.with({ transitions });
}
public setInitial(initial: string) {
@ -134,20 +125,15 @@ export class WorkflowDto extends Model<WorkflowDto> {
return this;
}
const transitions =
steps.length !== this.steps.length ?
this.transitions.filter(t => t.from !== name && t.to !== name) :
this.transitions;
let initial = this.initial;
const transitions = this.transitions.filter(t => t.from !== name && t.to !== name);
if (initial === name) {
if (this.initial === name) {
const first = steps.find(x => !x.isLocked);
initial = first ? first.name : null;
return this.with({ initial: first ? first.name : null, steps, transitions });
} else {
return this.with({ steps, transitions });
}
return this.with({ initial, steps, transitions });
}
public changeSchemaIds(schemaIds: string[]) {
@ -185,13 +171,11 @@ export class WorkflowDto extends Model<WorkflowDto> {
return transition;
});
let initial = this.initial;
if (initial === name) {
initial = newName;
if (this.initial === name) {
return this.with({ initial: newName, steps, transitions });
} else {
return this.with({ steps, transitions });
}
return this.with({ initial, steps, transitions });
}
public removeTransition(from: string, to: string) {
@ -204,32 +188,6 @@ export class WorkflowDto extends Model<WorkflowDto> {
return this.with({ transitions });
}
public setTransition(from: string, to: string, values: Partial<WorkflowTransitionValues> = {}) {
const stepFrom = this.getStep(from);
if (!stepFrom) {
return this;
}
const stepTo = this.getStep(to);
if (!stepTo) {
return this;
}
const found = this.transitions.find(x => x.from === from && x.to === to);
if (found) {
const { from: _, to: __, ...existing } = found;
values = { ...existing, ...values };
}
const transitions = [...this.transitions.filter(t => t !== found), { from, to, ...values }];
return this.with({ transitions });
}
public serialize(): any {
const result = { steps: {}, schemaIds: this.schemaIds, initial: this.initial, name: this.name };

2
src/Squidex/app/shell/pages/internal/apps-menu.component.html

@ -29,7 +29,7 @@
<div class="dropdown-divider"></div>
<div class="dropdown-button">
<button type="button" class="btn btn-block btn-success" (click)="createApp()">
<button type="button" class="btn btn-block btn-success" (click)="addAppDialog.show()">
<i class="icon-plus"></i> Create New App
</button>
</div>

5
src/Squidex/app/shell/pages/internal/apps-menu.component.ts

@ -36,11 +36,6 @@ export class AppsMenuComponent {
) {
}
public createApp() {
this.appsMenu.hide();
this.addAppDialog.show();
}
public trackByApp(index: number, app: AppDto) {
return app.id;
}

24
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -97,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft, false);
var command = new UpdateContent();
await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(content, contentWorkflow, command, false),
new ValidationError("Data is required.", "Data"));
}
@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft, false);
var command = new UpdateContent { Data = new NamedContentData() };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanUpdate(content, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanUpdate(content, contentWorkflow, command, false));
}
[Fact]
@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft, false);
var command = new UpdateContent { Data = new NamedContentData() };
await GuardContent.CanUpdate(content, contentWorkflow, command);
await GuardContent.CanUpdate(content, contentWorkflow, command, false);
}
[Fact]
@ -131,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft, false);
var command = new PatchContent();
await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(content, contentWorkflow, command, false),
new ValidationError("Data is required.", "Data"));
}
@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft, false);
var command = new PatchContent { Data = new NamedContentData() };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanPatch(content, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanPatch(content, contentWorkflow, command, false));
}
[Fact]
@ -154,7 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Draft, false);
var command = new PatchContent { Data = new NamedContentData() };
await GuardContent.CanPatch(content, contentWorkflow, command);
await GuardContent.CanPatch(content, contentWorkflow, command, false);
}
[Fact]
@ -163,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published, false);
var command = new ChangeContentStatus { Status = Status.Published };
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command, true),
new ValidationError("Content has no changes to publish.", "Status"));
}
@ -175,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published, false);
var command = new ChangeContentStatus { Status = Status.Draft };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command, false));
}
[Fact]
@ -186,7 +186,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
var content = CreateContent(Status.Published, true);
var command = new ChangeContentStatus { Status = Status.Published };
await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command);
await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command, true);
}
[Fact]
@ -198,7 +198,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, command.Status, user))
.Returns(true);
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command, false),
new ValidationError("Due time must be in the future.", "DueTime"));
}
@ -211,7 +211,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, command.Status, user))
.Returns(false);
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(schema, content, contentWorkflow, command, false),
new ValidationError("Cannot change status from Draft to Published.", "Status"));
}
@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, command.Status, user))
.Returns(true);
await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command);
await GuardContent.CanChangeStatus(schema, content, contentWorkflow, command, false);
}
[Fact]

Loading…
Cancel
Save