Browse Source

Improve workflow for save and publish.

pull/747/head
Sebastian 4 years ago
parent
commit
ad9130b64f
  1. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs
  2. 18
      backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs
  3. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs
  4. 2
      backend/src/Squidex.Web/Resources.cs
  5. 2
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  6. 15
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  7. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs
  8. 18
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
  9. 4
      frontend/app/features/administration/pages/restore/restore-page.component.html
  10. 4
      frontend/app/features/content/pages/content/content-page.component.html
  11. 8
      frontend/app/framework/angular/forms/editors/dropdown.component.html
  12. 3
      frontend/app/framework/angular/forms/editors/dropdown.component.scss

5
backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs

@ -52,6 +52,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
return Task.FromResult(Status.Draft);
}
public Task<bool> CanPublishInitialAsync(ISchemaEntity schema, ClaimsPrincipal? user)
{
return Task.FromResult(true);
}
public Task<bool> CanMoveToAsync(ISchemaEntity schema, Status status, Status next, ContentData data, ClaimsPrincipal? user)
{
var result = Flow.TryGetValue(status, out var step) && step.Transitions.Any(x => x.Status == next);

18
backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs

@ -35,6 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Contents
return workflow.Steps.Select(x => new StatusInfo(x.Key, GetColor(x.Value))).ToArray();
}
public async Task<bool> CanPublishInitialAsync(ISchemaEntity schema, ClaimsPrincipal? user)
{
var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id);
return workflow.TryGetTransition(workflow.Initial, Status.Published, out var transition) && IsTrue(transition, null, user);
}
public async Task<bool> CanMoveToAsync(ISchemaEntity schema, Status status, Status next, ContentData data, ClaimsPrincipal? user)
{
var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id);
@ -49,13 +56,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
return workflow.TryGetTransition(status, next, out var transition) && IsTrue(transition, content.Data, user);
}
public async Task<bool> CanPublishOnCreateAsync(ISchemaEntity schema, ContentData data, ClaimsPrincipal? user)
{
var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id);
return workflow.TryGetTransition(workflow.Initial, Status.Published, out var transition) && IsTrue(transition, data, user);
}
public async Task<bool> CanUpdateAsync(IContentEntity content, Status status, ClaimsPrincipal? user)
{
var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id);
@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return result.ToArray();
}
private bool IsTrue(WorkflowCondition condition, ContentData data, ClaimsPrincipal? user)
private bool IsTrue(WorkflowCondition condition, ContentData? data, ClaimsPrincipal? user)
{
if (condition?.Roles != null && user != null)
{
@ -116,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
}
if (!string.IsNullOrWhiteSpace(condition?.Expression))
if (!string.IsNullOrWhiteSpace(condition?.Expression) && data != null)
{
var vars = new ScriptVars
{

2
backend/src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs

@ -22,6 +22,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
Task<bool> CanUpdateAsync(IContentEntity content, Status status, ClaimsPrincipal? user);
Task<bool> CanPublishInitialAsync(ISchemaEntity schema, ClaimsPrincipal? user);
Task<StatusInfo?> GetInfoAsync(IContentEntity content, Status status);
Task<StatusInfo[]> GetNextAsync(IContentEntity content, Status status, ClaimsPrincipal? user);

2
backend/src/Squidex.Web/Resources.cs

@ -30,6 +30,8 @@ namespace Squidex.Web
public bool CanDeleteContentVersion(string schema) => IsAllowedForSchema(Permissions.AppContentsVersionDeleteOwn, schema);
public bool CanChangeStatus(string schema) => IsAllowedForSchema(Permissions.AppContentsChangeStatus, schema);
public bool CanCancelContentStatus(string schema) => IsAllowedForSchema(Permissions.AppContentsChangeStatusCancelOwn, schema);
public bool CanUpdateContent(string schema) => IsAllowedForSchema(Permissions.AppContentsUpdateOwn, schema);

2
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -173,7 +173,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
}
}
if (content.NextStatuses != null && resources.CanUpdateContent(schema))
if (content.NextStatuses != null && resources.CanChangeStatus(schema))
{
foreach (var next in content.NextStatuses)
{

15
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -47,7 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
{
await result.AssignStatusesAsync(workflow, schema);
result.CreateLinks(resources, schema.SchemaDef.Name);
await result.CreateLinksAsync(resources, workflow, schema);
}
return result;
@ -60,19 +60,22 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
Statuses = allStatuses.Select(StatusInfoDto.FromStatusInfo).ToArray();
}
private void CreateLinks(Resources resources, string schema)
private async Task CreateLinksAsync(Resources resources, IContentWorkflow workflow, ISchemaEntity schema)
{
var values = new { app = resources.App, schema };
var values = new { app = resources.App, schema = schema.SchemaDef.Name };
AddSelfLink(resources.Url<ContentsController>(x => nameof(x.GetContents), values));
if (resources.CanCreateContent(schema))
if (resources.CanCreateContent(values.schema))
{
AddPostLink("create", resources.Url<ContentsController>(x => nameof(x.PostContent), values));
var publishValues = new { values.app, values.schema, publish = true };
if (resources.CanChangeStatus(values.schema) && await workflow.CanPublishInitialAsync(schema, resources.Context.User))
{
var publishValues = new { values.app, values.schema, publish = true };
AddPostLink("create/publish", resources.Url<ContentsController>(x => nameof(x.PostContent), publishValues));
AddPostLink("create/publish", resources.Url<ContentsController>(x => nameof(x.PostContent), publishValues));
}
}
}
}

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs

@ -40,6 +40,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Equal(Status.Draft, result);
}
[Fact]
public async Task Should_allow_publish_on_create()
{
var result = await sut.CanPublishInitialAsync(null!, null);
Assert.True(result);
}
[Fact]
public async Task Should_allow_if_transition_is_valid()
{

18
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs

@ -133,29 +133,15 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_allow_publish_on_create()
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanPublishOnCreateAsync(Mocks.Schema(appId, schemaId), content.Data, Mocks.FrontendUser("Editor"));
var result = await sut.CanPublishInitialAsync(Mocks.Schema(appId, schemaId), Mocks.FrontendUser("Editor"));
Assert.True(result);
}
[Fact]
public async Task Should_not_allow_publish_on_create_if_data_is_invalid()
{
var content = CreateContent(Status.Draft, 4);
var result = await sut.CanPublishOnCreateAsync(Mocks.Schema(appId, schemaId), content.Data, Mocks.FrontendUser("Editor"));
Assert.False(result);
}
[Fact]
public async Task Should_not_allow_publish_on_create_if_role_not_allowed()
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanPublishOnCreateAsync(Mocks.Schema(appId, schemaId), content.Data, Mocks.FrontendUser("Developer"));
var result = await sut.CanPublishInitialAsync(Mocks.Schema(appId, schemaId), Mocks.FrontendUser("Developer"));
Assert.False(result);
}

4
frontend/app/features/administration/pages/restore/restore-page.component.html

@ -47,9 +47,13 @@
<form [formGroup]="restoreForm.form" (ngSubmit)="restore()">
<div class="row gx-2">
<div class="col">
<sqx-control-errors for="url"></sqx-control-errors>
<input class="form-control" formControlName="url" placeholder="{{ 'backups.restoreLastUrl' | sqxTranslate }}">
</div>
<div class="col">
<sqx-control-errors for="name"></sqx-control-errors>
<input class="form-control" formControlName="name" placeholder="{{ 'backups.restoreNewAppName' | sqxTranslate }}">
</div>
<div class="col-auto">

4
frontend/app/features/content/pages/content/content-page.component.html

@ -91,11 +91,11 @@
</ng-container>
<ng-template #noContentMenu>
<button type="button" class="btn btn-secondary" (click)="save()" *ngIf="contentsState.canCreate | async">
<button type="button" class="btn btn-primary" (click)="save()" *ngIf="contentsState.canCreate | async">
{{ 'common.save' | sqxTranslate }}
</button>
<button type="submit" class="btn btn-primary ms-2" title="i18n:common.saveShortcut" shortcut="CTRL + SHIFT + S" *ngIf="contentsState.canCreateAndPublish | async">
<button type="submit" class="btn btn-success ms-2" title="i18n:common.saveShortcut" shortcut="CTRL + SHIFT + S" *ngIf="contentsState.canCreateAndPublish | async">
{{ 'contents.saveAndPublish' | sqxTranslate }}
</button>
</ng-template>

8
frontend/app/framework/angular/forms/editors/dropdown.component.html

@ -5,9 +5,11 @@
autocapitalize="off">
<div class="control-dropdown-item" *ngIf="snapshot.selectedItem; let selectedItem">
<span class="truncate" *ngIf="!templateSelection">{{selectedItem}}</span>
<ng-template *ngIf="templateSelection" [sqxTemplateWrapper]="templateSelection" [item]="selectedItem"></ng-template>
<div>
<span class="truncate" *ngIf="!templateSelection">{{selectedItem}}</span>
<ng-template *ngIf="templateSelection" [sqxTemplateWrapper]="templateSelection" [item]="selectedItem"></ng-template>
</div>
</div>
</div>

3
frontend/app/framework/angular/forms/editors/dropdown.component.scss

@ -33,8 +33,11 @@ $color-input-disabled: #eef1f4;
.control-dropdown-item {
@include absolute(0, 1.75rem, 0, 0);
align-items: center;
display: flex;
line-height: 1.2rem;
overflow: hidden;
padding-top: 0;
padding-bottom: 0;
pointer-events: none;
position: absolute;

Loading…
Cancel
Save