From c49932a485dc181a78b6c9eea58719134fb34c57 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 14:28:54 +0200 Subject: [PATCH 01/19] Fix creating patterns. --- .../app/features/settings/pages/patterns/pattern.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts b/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts index 43572f708..b5cbec0a8 100644 --- a/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts +++ b/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts @@ -25,7 +25,7 @@ export class PatternComponent implements OnChanges { public editForm = new EditPatternForm(this.formBuilder); - public isEditable = false; + public isEditable = true; public isDeletable = false; constructor( From bd23785ff1c89d254196ab5b6b2df073bc0fa03b Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 14:41:45 +0200 Subject: [PATCH 02/19] Correct handling of duplicates. --- .../Controllers/Assets/AssetsController.cs | 5 ++++- .../components/assets-list.component.ts | 19 ++++++++++++++++--- .../app/shared/services/assets.service.ts | 4 ++++ .../app/shared/state/asset-uploader.state.ts | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 1db6f0659..8f0f95fa2 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -179,7 +179,10 @@ namespace Squidex.Areas.Api.Controllers.Assets var command = new CreateAsset { File = assetFile }; - var response = await InvokeCommandAsync(app, command); + var context = await CommandBus.PublishAsync(command); + + var result = context.Result(); + var response = AssetDto.FromAsset(result.Asset, this, app, result.IsDuplicate); return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response); } diff --git a/src/Squidex/app/shared/components/assets-list.component.ts b/src/Squidex/app/shared/components/assets-list.component.ts index 4838ef066..a2d10af80 100644 --- a/src/Squidex/app/shared/components/assets-list.component.ts +++ b/src/Squidex/app/shared/components/assets-list.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { onErrorResumeNext } from 'rxjs/operators'; import { @@ -38,10 +38,23 @@ export class AssetsListComponent { @Output() public select = new EventEmitter(); + constructor( + private readonly changeDetector: ChangeDetectorRef + ) { + } + public add(file: File, asset: AssetDto) { - this.newFiles = this.newFiles.remove(file); + if (asset.isDuplicate) { + setTimeout(() => { + this.newFiles = this.newFiles.remove(file); - this.state.add(asset); + this.changeDetector.detectChanges(); + }, 2000); + } else { + this.newFiles = this.newFiles.remove(file); + + this.state.add(asset); + } } public search() { diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts index 9e840e4cf..429c225e7 100644 --- a/src/Squidex/app/shared/services/assets.service.ts +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -43,6 +43,10 @@ export class AssetDto { public readonly canUpdate: boolean; public readonly canUpload: boolean; + public get isDuplicate() { + return this._meta && this._meta['isDuplicate'] === 'true'; + } + public get contentUrl() { return this._links['content'].href; } diff --git a/src/Squidex/app/shared/state/asset-uploader.state.ts b/src/Squidex/app/shared/state/asset-uploader.state.ts index 04f35ec25..4b16ade66 100644 --- a/src/Squidex/app/shared/state/asset-uploader.state.ts +++ b/src/Squidex/app/shared/state/asset-uploader.state.ts @@ -75,7 +75,7 @@ export class AssetUploaderState extends State { const stream = this.assetsService.uploadFile(this.appName, file); return this.upload(stream, MathHelper.guid(), file, asset => { - if (asset._meta && asset._meta['isDuplicate'] === 'true') { + if (asset.isDuplicate) { this.dialogs.notifyError('Asset has already been uploaded.'); } else if (target) { target.add(asset); From d7b511c852526106aef15d5a6bb6b95552a1c8a4 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 14:44:30 +0200 Subject: [PATCH 03/19] Confirmation before deleting asset. --- src/Squidex/app/shared/components/asset.component.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 583b66749..7fe2798ce 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -26,7 +26,10 @@ - + @@ -109,7 +112,10 @@ - diff --git a/src/Squidex/app/shared/components/schema-category.component.ts b/src/Squidex/app/shared/components/schema-category.component.ts index b5d714d9d..bd6479e09 100644 --- a/src/Squidex/app/shared/components/schema-category.component.ts +++ b/src/Squidex/app/shared/components/schema-category.component.ts @@ -24,6 +24,8 @@ interface State { schemasFiltered: ImmutableArray; schemasForCategory: ImmutableArray; + isDeletable: boolean; + isOpen: boolean; } @@ -64,6 +66,7 @@ export class SchemaCategoryComponent extends StatefulComponent implements private readonly schemasState: SchemasState ) { super(changeDetector, { + isDeletable: true, schemasFiltered: ImmutableArray.empty(), schemasForCategory: ImmutableArray.empty(), isOpen: true @@ -104,12 +107,15 @@ export class SchemaCategoryComponent extends StatefulComponent implements if (changes['name']) { let displayName = 'Schemas'; + let isDeletable = true; if (this.name && this.name.length > 0) { displayName = this.name; + } else { + isDeletable = false; } - this.next(s => ({ ...s, displayName })); + this.next(s => ({ ...s, isDeletable, displayName })); } } diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 0a0f09e24..f9cc8ba60 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -205,7 +205,7 @@ describe('SchemasService', () => { const resource: Resource = { _links: { - updateCategory: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/category' } + ['update/category']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/category' } } }; diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index d1a8caecb..fc0622809 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -310,7 +310,7 @@ export class SchemasService { } public putCategory(appName: string, resource: Resource, dto: UpdateSchemaCategoryDto, version: Version): Observable { - const link = resource._links['updateCategory']; + const link = resource._links['update/category']; const url = this.apiUrl.buildUrl(link.href); From 6c2a75bf1355817c1f40e292c93e7765390637c9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 15:25:07 +0200 Subject: [PATCH 07/19] Better fix for Categories. --- .../app/shared/components/schema-category.component.html | 2 +- .../app/shared/components/schema-category.component.ts | 8 +------- src/Squidex/app/shared/state/schemas.state.ts | 6 +++--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Squidex/app/shared/components/schema-category.component.html b/src/Squidex/app/shared/components/schema-category.component.html index 3bf359965..406e91f13 100644 --- a/src/Squidex/app/shared/components/schema-category.component.html +++ b/src/Squidex/app/shared/components/schema-category.component.html @@ -8,7 +8,7 @@

{{snapshot.displayName}} ({{snapshot.schemasFiltered.length}})

- diff --git a/src/Squidex/app/shared/components/schema-category.component.ts b/src/Squidex/app/shared/components/schema-category.component.ts index bd6479e09..b5d714d9d 100644 --- a/src/Squidex/app/shared/components/schema-category.component.ts +++ b/src/Squidex/app/shared/components/schema-category.component.ts @@ -24,8 +24,6 @@ interface State { schemasFiltered: ImmutableArray; schemasForCategory: ImmutableArray; - isDeletable: boolean; - isOpen: boolean; } @@ -66,7 +64,6 @@ export class SchemaCategoryComponent extends StatefulComponent implements private readonly schemasState: SchemasState ) { super(changeDetector, { - isDeletable: true, schemasFiltered: ImmutableArray.empty(), schemasForCategory: ImmutableArray.empty(), isOpen: true @@ -107,15 +104,12 @@ export class SchemaCategoryComponent extends StatefulComponent implements if (changes['name']) { let displayName = 'Schemas'; - let isDeletable = true; if (this.name && this.name.length > 0) { displayName = this.name; - } else { - isDeletable = false; } - this.next(s => ({ ...s, isDeletable, displayName })); + this.next(s => ({ ...s, displayName })); } } diff --git a/src/Squidex/app/shared/state/schemas.state.ts b/src/Squidex/app/shared/state/schemas.state.ts index f26604e47..3cdc39c85 100644 --- a/src/Squidex/app/shared/state/schemas.state.ts +++ b/src/Squidex/app/shared/state/schemas.state.ts @@ -140,7 +140,9 @@ export class SchemasState extends State { this.next(s => { const schemas = s.schemas.push(created).sortByStringAsc(x => x.displayName); - return { ...s, schemas }; + const categories = buildCategories(s.categories, schemas); + + return { ...s, schemas, categories }; }); }), shareSubscribed(this.dialogs, { silent: true })); @@ -343,8 +345,6 @@ function buildCategories(categories: { [name: string]: boolean }, schemas?: Sche } } - categories[''] = true; - return categories; } From 9b6e1a6cfa921161bce9f3bb3064dab993ed7a0e Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 15:28:33 +0200 Subject: [PATCH 08/19] Fix for preview urls. --- src/Squidex/app/shared/services/schemas.service.spec.ts | 2 +- src/Squidex/app/shared/services/schemas.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index f9cc8ba60..0daacd8e9 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -236,7 +236,7 @@ describe('SchemasService', () => { const resource: Resource = { _links: { - updateUrls: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/preview-urls' } + ['update/urls'] { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/preview-urls' } } }; diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index fc0622809..d74774141 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -325,7 +325,7 @@ export class SchemasService { } public putPreviewUrls(appName: string, resource: Resource, dto: {}, version: Version): Observable { - const link = resource._links['updateUrls']; + const link = resource._links['update/urls']; const url = this.apiUrl.buildUrl(link.href); From 275c601afe5e262c90f0c9ea1bb3ce0b771f56a6 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 15:33:56 +0200 Subject: [PATCH 09/19] Fix for disabling field. --- .../Schemas/Guards/GuardSchemaField.cs | 2 +- src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 93b152d8c..92ccf8142 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards throw new DomainException("Schema field is already disabled."); } - if (!field.IsForApi()) + if (!field.IsForApi(true)) { throw new DomainException("UI field cannot be disabled."); } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs index 97ad65125..16ad30e3c 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs @@ -83,7 +83,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models } else { - AddPutLink("show", controller.Url(x => nameof(x.DisableField), values)); + AddPutLink("disable", controller.Url(x => nameof(x.DisableField), values)); } if (Properties is ArrayFieldPropertiesDto) From f8520d2bc1bceafe7d8b1f861dc8a3973c3e86c0 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 15:47:02 +0200 Subject: [PATCH 10/19] Field ordering fixed. --- .../Areas/Api/Controllers/Schemas/Models/FieldDto.cs | 6 ++++-- .../Areas/Api/Controllers/Schemas/Models/SchemaDto.cs | 2 +- .../app/features/schemas/pages/schema/field.component.ts | 2 +- .../features/schemas/pages/schema/schema-page.component.ts | 2 +- src/Squidex/app/shared/services/schemas.service.spec.ts | 4 ++-- src/Squidex/app/shared/services/schemas.service.ts | 4 ++-- src/Squidex/app/shared/state/schemas.state.spec.ts | 4 ++-- src/Squidex/app/shared/state/schemas.state.ts | 2 +- 8 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs index 16ad30e3c..bc583bb03 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs @@ -88,9 +88,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models if (Properties is ArrayFieldPropertiesDto) { - AddPostLink("fields/add", controller.Url(x => nameof(x.PostNestedField), values)); + var parentValues = new { app, name = schema, parentId = FieldId }; - AddPutLink("order", controller.Url(x => nameof(x.PutNestedFieldOrdering), values)); + AddPostLink("fields/add", controller.Url(x => nameof(x.PostNestedField), parentValues)); + + AddPutLink("fields/order", controller.Url(x => nameof(x.PutNestedFieldOrdering), parentValues)); } AddPutLink("lock", controller.Url(x => nameof(x.LockField), values)); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index 7a48b73d9..35fafd420 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -119,7 +119,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models if (allowUpdate) { - AddPutLink("order", controller.Url(x => nameof(x.PutSchemaFieldOrdering), values)); + AddPutLink("fields/order", controller.Url(x => nameof(x.PutSchemaFieldOrdering), values)); AddPutLink("update", controller.Url(x => nameof(x.PutSchema), values)); AddPutLink("update/category", controller.Url(x => nameof(x.PutCategory), values)); diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.ts b/src/Squidex/app/features/schemas/pages/schema/field.component.ts index db2288385..848aef0f8 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.ts @@ -95,7 +95,7 @@ export class FieldComponent implements OnChanges { } public sortFields(fields: NestedFieldDto[]) { - this.schemasState.sortFields(this.schema, fields, this.field).subscribe(); + this.schemasState.orderFields(this.schema, fields, this.field).subscribe(); } public lockField() { diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index abc5b1f46..d9957a1f0 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -87,7 +87,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit { } public sortFields(fields: FieldDto[]) { - this.schemasState.sortFields(this.schema, fields).subscribe(); + this.schemasState.orderFields(this.schema, fields).subscribe(); } public trackByField(index: number, field: FieldDto) { diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 0daacd8e9..cec206ce2 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -236,7 +236,7 @@ describe('SchemasService', () => { const resource: Resource = { _links: { - ['update/urls'] { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/preview-urls' } + ['update/urls']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/preview-urls' } } }; @@ -387,7 +387,7 @@ describe('SchemasService', () => { const resource: Resource = { _links: { - order: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/fields/ordering' } + ['fields/order']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/fields/ordering' } } }; diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index d74774141..e02391b2a 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -385,11 +385,11 @@ export class SchemasService { } public putFieldOrdering(appName: string, resource: Resource, dto: number[], version: Version): Observable { - const link = resource._links['order']; + const link = resource._links['fields/order']; const url = this.apiUrl.buildUrl(link.href); - return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( + return HTTP.requestVersioned(this.http, link.method, url, version, { fieldIds: dto }).pipe( map(({ version: newVersion, payload }) => { return parseSchemaWithDetails(payload.body, newVersion); }), diff --git a/src/Squidex/app/shared/state/schemas.state.spec.ts b/src/Squidex/app/shared/state/schemas.state.spec.ts index 394599074..8668a17f8 100644 --- a/src/Squidex/app/shared/state/schemas.state.spec.ts +++ b/src/Squidex/app/shared/state/schemas.state.spec.ts @@ -373,7 +373,7 @@ describe('SchemasState', () => { schemasService.setup(x => x.putFieldOrdering(app, schema1, [schema.fields[1].fieldId, schema.fields[2].fieldId], version)) .returns(() => of(updated)).verifiable(); - schemasState.sortFields(schema1, [schema.fields[1], schema.fields[2]]).subscribe(); + schemasState.orderFields(schema1, [schema.fields[1], schema.fields[2]]).subscribe(); const schema1New = schemasState.snapshot.schemas.at(0); @@ -387,7 +387,7 @@ describe('SchemasState', () => { schemasService.setup(x => x.putFieldOrdering(app, schema.fields[0], [schema.fields[1].fieldId, schema.fields[2].fieldId], version)) .returns(() => of(updated)).verifiable(); - schemasState.sortFields(schema1, [schema.fields[1], schema.fields[2]], schema.fields[0]).subscribe(); + schemasState.orderFields(schema1, [schema.fields[1], schema.fields[2]], schema.fields[0]).subscribe(); const schema1New = schemasState.snapshot.schemas.at(0); diff --git a/src/Squidex/app/shared/state/schemas.state.ts b/src/Squidex/app/shared/state/schemas.state.ts index 3cdc39c85..68c3ea6d5 100644 --- a/src/Squidex/app/shared/state/schemas.state.ts +++ b/src/Squidex/app/shared/state/schemas.state.ts @@ -233,7 +233,7 @@ export class SchemasState extends State { shareMapSubscribed(this.dialogs, x => getField(x, request, parent), { silent: true })); } - public sortFields(schema: SchemaDto, fields: any[], parent?: RootFieldDto): Observable { + public orderFields(schema: SchemaDto, fields: any[], parent?: RootFieldDto): Observable { return this.schemasService.putFieldOrdering(this.appName, parent || schema, fields.map(t => t.fieldId), schema.version).pipe( tap(updated => { this.replaceSchema(updated); From d01640e13e0028834fbfec00f107d8602ad5e96b Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 16:01:14 +0200 Subject: [PATCH 11/19] Fix for selecting content. --- .../Areas/Api/Controllers/Contents/Models/ContentDto.cs | 2 +- .../content/pages/content/content-page.component.ts | 2 +- .../features/content/shared/content-item.component.html | 2 +- .../app/shared/components/markdown-editor.component.ts | 8 ++++++++ .../app/shared/components/rich-editor.component.ts | 4 ++++ 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 3939cfd67..b2db0db33 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -142,7 +142,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models if (controller.HasPermission(Permissions.AppContentsDelete, app, schema)) { - AddPutLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); } foreach (var next in StatusFlow.Next(Status)) diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index 94c3f79c2..fd4afb1a6 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -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.canUpdate); } public discardChanges() { diff --git a/src/Squidex/app/features/content/shared/content-item.component.html b/src/Squidex/app/features/content/shared/content-item.component.html index 1b84a7d20..2569c05e1 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -11,7 +11,7 @@ - + diff --git a/src/Squidex/app/shared/components/markdown-editor.component.ts b/src/Squidex/app/shared/components/markdown-editor.component.ts index bc7ed55a6..2197c1fdd 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.ts +++ b/src/Squidex/app/shared/components/markdown-editor.component.ts @@ -78,6 +78,10 @@ export class MarkdownEditorComponent extends StatefulControlComponent { + if (this.isDisabled) { + return; + } + this.assetsDialog.show(); } @@ -220,6 +224,10 @@ export class MarkdownEditorComponent extends StatefulControlComponent i } private showSelector = () => { + if (this.isDisabled) { + return; + } + this.assetsDialog.show(); } From 1254cf98f17e2e81211fe8a2b1b142d14df6aafc Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 20 Jun 2019 16:44:48 +0200 Subject: [PATCH 12/19] Tests fixed. --- src/Squidex/app/shared/state/schemas.state.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Squidex/app/shared/state/schemas.state.spec.ts b/src/Squidex/app/shared/state/schemas.state.spec.ts index 8668a17f8..4e8c74c74 100644 --- a/src/Squidex/app/shared/state/schemas.state.spec.ts +++ b/src/Squidex/app/shared/state/schemas.state.spec.ts @@ -69,7 +69,7 @@ describe('SchemasState', () => { expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items); expect(schemasState.snapshot.isLoaded).toBeTruthy(); - expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, '': true }); + expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false }); schemasService.verifyAll(); }); @@ -83,7 +83,7 @@ describe('SchemasState', () => { expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas.items); expect(schemasState.snapshot.isLoaded).toBeTruthy(); - expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true, '': true }); + expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true }); schemasService.verifyAll(); }); @@ -111,13 +111,13 @@ describe('SchemasState', () => { it('should add category', () => { schemasState.addCategory('category3'); - expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true, '': true }); + expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true }); }); it('should remove category', () => { schemasState.removeCategory('category1'); - expect(schemasState.snapshot.categories).toEqual({ 'category2': false, '': true }); + expect(schemasState.snapshot.categories).toEqual({ 'category2': false }); }); it('should return schema on select and reload when already loaded', () => { From 8d4246ab828c86b4e22e54f1a7427ed65e6ebc39 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 14:58:57 +0200 Subject: [PATCH 13/19] Fixed content changed trigger. --- .../rules/triggers/content-changed-trigger.component.html | 8 ++++---- .../rules/triggers/content-changed-trigger.component.ts | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html index f1ae6a0f7..da925bae6 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html @@ -22,13 +22,13 @@ - @@ -38,12 +38,12 @@
-
- +
diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts index f169a8ff6..a3e1f140b 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts @@ -29,9 +29,6 @@ export class ContentChangedTriggerComponent implements OnInit { @Input() public schemas: ImmutableArray; - @Input() - public isEditable: boolean; - @Input() public trigger: any; From 5c83dcec1649f2044d1d4dc7b09fd220230f0ad7 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 15:57:48 +0200 Subject: [PATCH 14/19] Fix tag names. --- .../Assets/AssetCommandMiddleware.cs | 64 ++++++++++---- .../Assets/AssetCreatedResult.cs | 11 ++- .../Assets/AssetResult.cs | 25 ++++++ .../Assets/Commands/CreateAsset.cs | 9 +- .../Assets/Commands/UpdateAsset.cs | 9 +- .../Assets/Commands/UploadAssetCommand.cs | 20 +++++ .../CollectionExtensions.cs | 8 ++ .../Controllers/Assets/AssetsController.cs | 6 +- .../Api/Controllers/Assets/Models/AssetDto.cs | 7 +- .../app/shared/services/assets.service.ts | 2 +- .../Assets/AssetCommandMiddlewareTests.cs | 84 ++++++++++++++++--- 11 files changed, 189 insertions(+), 56 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index b13c99f29..e45e02645 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Threading.Tasks; using Orleans; +using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Tags; using Squidex.Infrastructure; @@ -24,25 +25,28 @@ namespace Squidex.Domain.Apps.Entities.Assets private readonly IAssetQueryService assetQuery; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; private readonly IEnumerable> tagGenerators; + private readonly ITagService tagService; public AssetCommandMiddleware( IGrainFactory grainFactory, IAssetQueryService assetQuery, IAssetStore assetStore, IAssetThumbnailGenerator assetThumbnailGenerator, - IEnumerable> tagGenerators) + IEnumerable> tagGenerators, + ITagService tagService) : base(grainFactory) { Guard.NotNull(assetStore, nameof(assetStore)); Guard.NotNull(assetQuery, nameof(assetQuery)); Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); Guard.NotNull(tagGenerators, nameof(tagGenerators)); + Guard.NotNull(tagService, nameof(tagService)); this.assetStore = assetStore; this.assetQuery = assetQuery; this.assetThumbnailGenerator = assetThumbnailGenerator; - this.tagGenerators = tagGenerators; + this.tagService = tagService; } public override async Task HandleAsync(CommandContext context, Func next) @@ -56,9 +60,8 @@ namespace Squidex.Domain.Apps.Entities.Assets createAsset.Tags = new HashSet(); } - createAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(createAsset.File.OpenRead()); - - createAsset.FileHash = await UploadAsync(context, createAsset.File); + await EnrichWithImageInfosAsync(createAsset); + await EnrichWithHashAndUploadAsync(createAsset, context); try { @@ -70,7 +73,9 @@ namespace Squidex.Domain.Apps.Entities.Assets { if (IsDuplicate(createAsset, existing)) { - result = new AssetCreatedResult(existing, true); + var denormalizedTags = await tagService.DenormalizeTagsAsync(createAsset.AppId.Id, TagGroups.Assets, existing.Tags); + + result = new AssetCreatedResult(existing, true, new HashSet(denormalizedTags.Values)); } break; @@ -85,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Assets var asset = (IAssetEntity)await ExecuteCommandAsync(createAsset); - result = new AssetCreatedResult(asset, false); + result = new AssetCreatedResult(asset, false, createAsset.Tags); await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null); } @@ -102,16 +107,16 @@ namespace Squidex.Domain.Apps.Entities.Assets case UpdateAsset updateAsset: { - updateAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(updateAsset.File.OpenRead()); + await EnrichWithImageInfosAsync(updateAsset); + await EnrichWithHashAndUploadAsync(updateAsset, context); - updateAsset.FileHash = await UploadAsync(context, updateAsset.File); try { - var result = (IAssetEntity)await ExecuteCommandAsync(updateAsset); + var result = (AssetResult)await ExecuteAndAdjustTagsAsync(updateAsset); context.Complete(result); - await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.FileVersion, null); + await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.Asset.FileVersion, null); } finally { @@ -121,29 +126,54 @@ namespace Squidex.Domain.Apps.Entities.Assets break; } + case AssetCommand command: + { + var result = await ExecuteAndAdjustTagsAsync(command); + + context.Complete(result); + + break; + } + default: await base.HandleAsync(context, next); + break; } } + private async Task ExecuteAndAdjustTagsAsync(AssetCommand command) + { + var result = await ExecuteCommandAsync(command); + + if (result is IAssetEntity asset) + { + var denormalizedTags = await tagService.DenormalizeTagsAsync(asset.AppId.Id, TagGroups.Assets, asset.Tags); + + return new AssetResult(asset, new HashSet(denormalizedTags.Values)); + } + + return result; + } + private static bool IsDuplicate(CreateAsset createAsset, IAssetEntity asset) { return asset != null && asset.FileName == createAsset.File.FileName && asset.FileSize == createAsset.File.FileSize; } - private async Task UploadAsync(CommandContext context, AssetFile file) + private async Task EnrichWithImageInfosAsync(UploadAssetCommand command) { - string hash; + command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); + } - using (var hashStream = new HasherStream(file.OpenRead(), HashAlgorithmName.SHA256)) + private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, CommandContext context) + { + using (var hashStream = new HasherStream(command.File.OpenRead(), HashAlgorithmName.SHA256)) { await assetStore.UploadAsync(context.ContextId.ToString(), hashStream); - hash = $"{hashStream.GetHashStringAndReset()}{file.FileName}{file.FileSize}".Sha256Base64(); + command.FileHash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64(); } - - return hash; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs index b1502786a..9ccc00763 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs @@ -5,18 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; + namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetCreatedResult + public sealed class AssetCreatedResult : AssetResult { - public IAssetEntity Asset { get; } - public bool IsDuplicate { get; } - public AssetCreatedResult(IAssetEntity asset, bool isDuplicate) + public AssetCreatedResult(IAssetEntity asset, bool isDuplicate, HashSet tags) + : base(asset, tags) { - Asset = asset; - IsDuplicate = isDuplicate; } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs new file mode 100644 index 000000000..b43713da5 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public class AssetResult + { + public IAssetEntity Asset { get; } + + public HashSet Tags { get; } + + public AssetResult(IAssetEntity asset, HashSet tags) + { + Asset = asset; + + Tags = tags; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs index 9c49e67bd..8e869ba40 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs @@ -8,22 +8,15 @@ using System; using System.Collections.Generic; using Squidex.Infrastructure; -using Squidex.Infrastructure.Assets; namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class CreateAsset : AssetCommand, IAppCommand + public sealed class CreateAsset : UploadAssetCommand, IAppCommand { public NamedId AppId { get; set; } - public AssetFile File { get; set; } - - public ImageInfo ImageInfo { get; set; } - public HashSet Tags { get; set; } - public string FileHash { get; set; } - public CreateAsset() { AssetId = Guid.NewGuid(); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs index 1c998ac7a..16197164d 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs @@ -5,16 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.Assets; - namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class UpdateAsset : AssetCommand + public sealed class UpdateAsset : UploadAssetCommand { - public AssetFile File { get; set; } - - public ImageInfo ImageInfo { get; set; } - - public string FileHash { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs new file mode 100644 index 000000000..5ef0652cd --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Assets; + +namespace Squidex.Domain.Apps.Entities.Assets.Commands +{ + public abstract class UploadAssetCommand : AssetCommand + { + public AssetFile File { get; set; } + + public ImageInfo ImageInfo { get; set; } + + public string FileHash { get; set; } + } +} diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs index 76be0fd06..cfe546a24 100644 --- a/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -13,6 +13,14 @@ namespace Squidex.Infrastructure { public static class CollectionExtensions { + public static void AddRange(this ICollection target, IEnumerable source) + { + foreach (var value in source) + { + target.Add(value); + } + } + public static IEnumerable Shuffle(this IEnumerable enumerable) { var random = new Random(); diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 8f0f95fa2..965056fbe 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -182,7 +182,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var context = await CommandBus.PublishAsync(command); var result = context.Result(); - var response = AssetDto.FromAsset(result.Asset, this, app, result.IsDuplicate); + var response = AssetDto.FromAsset(result.Asset, this, app, result.Tags, result.IsDuplicate); return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response); } @@ -267,8 +267,8 @@ namespace Squidex.Areas.Api.Controllers.Assets { var context = await CommandBus.PublishAsync(command); - var result = context.Result(); - var response = AssetDto.FromAsset(result, this, app); + var result = context.Result(); + var response = AssetDto.FromAsset(result.Asset, this, app, result.Tags); return response; } diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index f44c9d25a..ec0e68899 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs @@ -118,10 +118,15 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models [JsonProperty("_meta")] public AssetMetadata Metadata { get; set; } - public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, bool isDuplicate = false) + public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, HashSet tags = null, bool isDuplicate = false) { var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); + if (tags != null) + { + response.Tags = tags; + } + if (isDuplicate) { response.Metadata = new AssetMetadata { IsDuplicate = "true" }; diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts index 429c225e7..8fd9e0fbd 100644 --- a/src/Squidex/app/shared/services/assets.service.ts +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -242,7 +242,7 @@ export class AssetsService { tap(() => { this.analytics.trackEvent('Analytics', 'Updated', appName); }), - pretifyError('Failed to delete asset. Please reload.')); + pretifyError('Failed to update asset. Please reload.')); } public deleteAsset(appName: string, asset: Resource, version: Version): Observable> { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index 21df10911..00f6856ed 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs @@ -54,19 +54,23 @@ namespace Squidex.Domain.Apps.Entities.Assets A.CallTo(() => assetQuery.QueryByHashAsync(AppId, A.Ignored)) .Returns(new List()); - A.CallTo(() => tagService.NormalizeTagsAsync(AppId, TagGroups.Assets, A>.Ignored, A>.Ignored)) - .Returns(new Dictionary()); + A.CallTo(() => tagService.DenormalizeTagsAsync(AppId, TagGroups.Assets, A>.Ignored)) + .Returns(new Dictionary + { + ["1"] = "foundTag1", + ["2"] = "foundTag2" + }); A.CallTo(() => grainFactory.GetGrain(Id, null)) .Returns(asset); - sut = new AssetCommandMiddleware(grainFactory, assetQuery, assetStore, assetThumbnailGenerator, new[] { tagGenerator }); + sut = new AssetCommandMiddleware(grainFactory, assetQuery, assetStore, assetThumbnailGenerator, new[] { tagGenerator }, tagService); } [Fact] public async Task Create_should_create_domain_object() { - var command = new CreateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupTags(command); @@ -80,6 +84,8 @@ namespace Squidex.Domain.Apps.Entities.Assets Assert.Contains("tag1", command.Tags); Assert.Contains("tag2", command.Tags); + Assert.Equal(new HashSet { "tag1", "tag2" }, result.Tags); + AssertAssetHasBeenUploaded(0, context.ContextId); AssertAssetImageChecked(); } @@ -87,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Assets [Fact] public async Task Create_should_calculate_hash() { - var command = new CreateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupImageInfo(); @@ -100,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Assets [Fact] public async Task Create_should_return_duplicate_result_if_file_with_same_hash_found() { - var command = new CreateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupSameHashAsset(file.FileName, file.FileSize, out _); @@ -108,13 +114,15 @@ namespace Squidex.Domain.Apps.Entities.Assets await sut.HandleAsync(context); - Assert.True(context.Result().IsDuplicate); + var result = context.Result(); + + Assert.True(result.IsDuplicate); } [Fact] public async Task Create_should_not_return_duplicate_result_if_file_with_same_hash_but_other_name_found() { - var command = new CreateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupSameHashAsset("other-name", file.FileSize, out _); @@ -122,13 +130,31 @@ namespace Squidex.Domain.Apps.Entities.Assets await sut.HandleAsync(context); - Assert.False(context.Result().IsDuplicate); + var result = context.Result(); + + Assert.False(result.IsDuplicate); + } + + [Fact] + public async Task Create_should_resolve_tag_names_for_duplicate() + { + var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); + var context = CreateContextForCommand(command); + + SetupSameHashAsset(file.FileName, file.FileSize, out _); + SetupImageInfo(); + + await sut.HandleAsync(context); + + var result = context.Result(); + + Assert.Equal(new HashSet { "foundTag1", "foundTag2" }, result.Tags); } [Fact] public async Task Create_should_not_return_duplicate_result_if_file_with_same_hash_but_other_size_found() { - var command = new CreateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupSameHashAsset(file.FileName, 12345, out _); @@ -142,7 +168,7 @@ namespace Squidex.Domain.Apps.Entities.Assets [Fact] public async Task Update_should_update_domain_object() { - var command = new UpdateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new UpdateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupImageInfo(); @@ -158,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Assets [Fact] public async Task Update_should_calculate_hash() { - var command = new UpdateAsset { AssetId = assetId, File = file }; + var command = CreateCommand(new UpdateAsset { AssetId = assetId, File = file }); var context = CreateContextForCommand(command); SetupImageInfo(); @@ -170,6 +196,40 @@ namespace Squidex.Domain.Apps.Entities.Assets Assert.True(command.FileHash.Length > 10); } + [Fact] + public async Task Update_should_resolve_tags() + { + var command = CreateCommand(new UpdateAsset { AssetId = assetId, File = file }); + var context = CreateContextForCommand(command); + + SetupImageInfo(); + + await ExecuteCreateAsync(); + + await sut.HandleAsync(context); + + var result = context.Result(); + + Assert.Equal(new HashSet { "foundTag1", "foundTag2" }, result.Tags); + } + + [Fact] + public async Task AnnotateAsset_should_resolve_tags() + { + var command = CreateCommand(new AnnotateAsset { AssetId = assetId, FileName = "newName" }); + var context = CreateContextForCommand(command); + + SetupImageInfo(); + + await ExecuteCreateAsync(); + + await sut.HandleAsync(context); + + var result = context.Result(); + + Assert.Equal(new HashSet { "foundTag1", "foundTag2" }, result.Tags); + } + private Task ExecuteCreateAsync() { return asset.ExecuteAsync(CreateCommand(new CreateAsset { AssetId = Id, File = file })); From ad0ab894fbda4d2d849be24b05417bfa21e4b7d0 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 17:01:58 +0200 Subject: [PATCH 15/19] Fix field form after "Create and Edit" --- .../features/schemas/pages/schema/field-wizard.component.html | 1 + src/Squidex/app/shared/components/schema-category.component.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html index 1b4866640..cf9681be6 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html @@ -65,6 +65,7 @@
implements if (query) { isOpen = true; } else { - isOpen = this.localStore.get(`schema-category.${this.name}`) !== 'false'; + isOpen = !this.localStore.getBoolean(this.configKey()); } this.next(s => ({ ...s, isOpen, schemasFiltered, schemasForCategory })); From c00305444905c2f575e1f4537259eda18b6e59a3 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 17:07:35 +0200 Subject: [PATCH 16/19] Fixed field deletion. --- .../Areas/Api/Controllers/Schemas/SchemaFieldsController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index 55e68584b..6c50117dc 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -513,9 +513,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas [ApiCosts(1)] public async Task DeleteNestedField(string app, string name, long parentId, long id) { - await CommandBus.PublishAsync(new DeleteField { ParentFieldId = parentId, FieldId = id }); + var command = new DeleteField { ParentFieldId = parentId, FieldId = id }; - return NoContent(); + var response = await InvokeCommandAsync(app, command); + + return Ok(response); } private async Task InvokeCommandAsync(string app, ICommand command) From 2750c74b732852b24a300e173435d858cf4966ea Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 17:22:05 +0200 Subject: [PATCH 17/19] Fix cancel and enqueue rules. --- .../Controllers/Rules/Models/RuleEventDto.cs | 5 +- .../app/shared/services/rules.service.spec.ts | 67 ++++++++++--------- .../app/shared/services/rules.service.ts | 12 +++- .../shared/state/rule-events.state.spec.ts | 8 +-- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs index 201e3dadd..a513495ab 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs @@ -80,7 +80,10 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models AddPutLink("update", controller.Url(x => nameof(x.PutEvent), values)); - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteEvent), values)); + if (NextAttempt.HasValue) + { + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteEvent), values)); + } return this; } diff --git a/src/Squidex/app/shared/services/rules.service.spec.ts b/src/Squidex/app/shared/services/rules.service.spec.ts index 399580e53..99c00490a 100644 --- a/src/Squidex/app/shared/services/rules.service.spec.ts +++ b/src/Squidex/app/shared/services/rules.service.spec.ts @@ -288,41 +288,15 @@ describe('RulesService', () => { req.flush({ total: 20, items: [ - { - id: 'id1', - created: '2017-12-12T10:10', - eventName: 'event1', - nextAttempt: '2017-12-12T12:10', - jobResult: 'Failed', - lastDump: 'dump1', - numCalls: 1, - description: 'url1', - result: 'Failed' - }, - { - id: 'id2', - created: '2017-12-13T10:10', - eventName: 'event2', - nextAttempt: '2017-12-13T12:10', - jobResult: 'Failed', - lastDump: 'dump2', - numCalls: 2, - description: 'url2', - result: 'Failed' - } + ruleEventResponse(1), + ruleEventResponse(2) ] }); expect(rules!).toEqual( new RuleEventsDto(20, [ - new RuleEventDto('id1', - DateTime.parseISO_UTC('2017-12-12T10:10'), - DateTime.parseISO_UTC('2017-12-12T12:10'), - 'event1', 'url1', 'dump1', 'Failed', 'Failed', 1), - new RuleEventDto('id2', - DateTime.parseISO_UTC('2017-12-13T10:10'), - DateTime.parseISO_UTC('2017-12-13T12:10'), - 'event2', 'url2', 'dump2', 'Failed', 'Failed', 2) + createRuleEvent(1), + createRuleEvent(2) ])); })); @@ -364,6 +338,23 @@ describe('RulesService', () => { req.flush({}); })); + function ruleEventResponse(id: number, suffix = '') { + return { + id: `id${id}`, + created: `${id % 1000 + 2000}-12-12T10:10:00`, + eventName: `event${id}${suffix}`, + nextAttempt: `${id % 1000 + 2000}-11-11T10:10`, + jobResult: `Failed${id}${suffix}`, + lastDump: `dump${id}${suffix}`, + numCalls: id, + description: `url${id}${suffix}`, + result: `Failed${id}${suffix}`, + _links: { + update: { method: 'PUT', href: `/rules/events/${id}` } + } + }; + } + function ruleResponse(id: number, suffix = '') { return { id: `id${id}`, @@ -390,6 +381,22 @@ describe('RulesService', () => { } }); +export function createRuleEvent(id: number, suffix = '') { + const links: ResourceLinks = { + update: { method: 'PUT', href: `/rules/events/${id}` } + }; + + return new RuleEventDto(links, `id${id}`, + DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`), + DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`), + `event${id}${suffix}`, + `url${id}${suffix}`, + `dump${id}${suffix}`, + `Failed${id}${suffix}`, + `Failed${id}${suffix}`, + id); +} + export function createRule(id: number, suffix = '') { const links: ResourceLinks = { update: { method: 'PUT', href: `/rules/${id}` } diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index bd44ae278..5c42f7dec 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -127,7 +127,10 @@ export class RuleEventsDto extends ResultSet { export class RuleEventDto extends Model { public readonly _links: ResourceLinks; - constructor( + public readonly canDelete: boolean; + public readonly canUpdate: boolean; + + constructor(links: ResourceLinks, public readonly id: string, public readonly created: DateTime, public readonly nextAttempt: DateTime | null, @@ -139,6 +142,11 @@ export class RuleEventDto extends Model { public readonly numCalls: number ) { super(); + + this._links = links; + + this.canDelete = hasAnyLink(links, 'delete'); + this.canUpdate = hasAnyLink(links, 'update'); } } @@ -287,7 +295,7 @@ export class RulesService { const items: any[] = body.items; const ruleEvents = new RuleEventsDto(body.total, items.map(item => - new RuleEventDto( + new RuleEventDto(item._links, item.id, DateTime.parseISO_UTC(item.created), item.nextAttempt ? DateTime.parseISO_UTC(item.nextAttempt) : null, diff --git a/src/Squidex/app/shared/state/rule-events.state.spec.ts b/src/Squidex/app/shared/state/rule-events.state.spec.ts index 5adcd6f4a..cd0ebcab6 100644 --- a/src/Squidex/app/shared/state/rule-events.state.spec.ts +++ b/src/Squidex/app/shared/state/rule-events.state.spec.ts @@ -9,14 +9,14 @@ import { of } from 'rxjs'; import { IMock, It, Mock, Times } from 'typemoq'; import { - DateTime, DialogService, - RuleEventDto, RuleEventsDto, RuleEventsState, RulesService } from '@app/shared/internal'; +import { createRuleEvent } from '../services/rules.service.spec'; + import { TestValues } from './_test-helpers'; describe('RuleEventsState', () => { @@ -26,8 +26,8 @@ describe('RuleEventsState', () => { } = TestValues; const oldRuleEvents = [ - new RuleEventDto('id1', DateTime.now(), null, 'event1', 'description', 'dump1', 'result1', 'result1', 1), - new RuleEventDto('id2', DateTime.now(), null, 'event2', 'description', 'dump2', 'result2', 'result2', 2) + createRuleEvent(1), + createRuleEvent(2) ]; let dialogs: IMock; From 85cd8f539d8de76249862c5e1b925374345bff79 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 17:26:39 +0200 Subject: [PATCH 18/19] Due time fixed. --- src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs b/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs index 9936e98ef..69c580518 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs @@ -22,17 +22,17 @@ namespace Squidex.Domain.Apps.Entities.Contents public Instant DueTime { get; } - public ScheduleJob(Guid id, Status status, RefToken scheduledBy, Instant due) + public ScheduleJob(Guid id, Status status, RefToken scheduledBy, Instant dueTime) { Id = id; ScheduledBy = scheduledBy; Status = status; - DueTime = due; + DueTime = dueTime; } - public static ScheduleJob Build(Status status, RefToken by, Instant due) + public static ScheduleJob Build(Status status, RefToken by, Instant dueTime) { - return new ScheduleJob(Guid.NewGuid(), status, by, due); + return new ScheduleJob(Guid.NewGuid(), status, by, dueTime); } } } From 362e8d71c63216bc61bf29a0da31a8b33fe9966d Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 21 Jun 2019 17:58:59 +0200 Subject: [PATCH 19/19] Fix for Create and Edit with nested field. --- .../app/shared/services/schemas.service.ts | 2 +- .../app/shared/state/schemas.state.spec.ts | 17 ++++++++++++++--- src/Squidex/app/shared/state/schemas.state.ts | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index e02391b2a..d2f6f7517 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -213,7 +213,7 @@ export class SchemaPropertiesDto { export interface AddFieldDto { readonly name: string; - readonly partitioning: string; + readonly partitioning?: string; readonly properties: FieldPropertiesDto; } diff --git a/src/Squidex/app/shared/state/schemas.state.spec.ts b/src/Squidex/app/shared/state/schemas.state.spec.ts index 4e8c74c74..0f0315218 100644 --- a/src/Squidex/app/shared/state/schemas.state.spec.ts +++ b/src/Squidex/app/shared/state/schemas.state.spec.ts @@ -13,6 +13,7 @@ import { SchemasState } from './schemas.state'; import { DialogService, + FieldDto, SchemaDetailsDto, SchemasService, UpdateSchemaCategoryDto, @@ -329,28 +330,38 @@ describe('SchemasState', () => { schemasService.setup(x => x.postField(app, schema1, It.isAny(), version)) .returns(() => of(updated)).verifiable(); - schemasState.addField(schema1, request).subscribe(); + let newField: FieldDto; + + schemasState.addField(schema1, request).subscribe(result => { + newField = result; + }); const schema1New = schemasState.snapshot.schemas.at(0); expect(schema1New).toEqual(updated); expect(schemasState.snapshot.selectedSchema).toEqual(updated); + expect(newField!).toBeDefined(); }); it('should update schema and selected schema when nested field added', () => { - const request = { ...schema.fields[0] }; + const request = { ...schema.fields[0].nested[0] }; const updated = createSchemaDetails(1, newVersion, '-new'); schemasService.setup(x => x.postField(app, schema.fields[0], It.isAny(), version)) .returns(() => of(updated)).verifiable(); - schemasState.addField(schema1, request, schema.fields[0]).subscribe(); + let newField: FieldDto; + + schemasState.addField(schema1, request, schema.fields[0]).subscribe(result => { + newField = result; + }); const schema1New = schemasState.snapshot.schemas.at(0); expect(schema1New).toEqual(updated); expect(schemasState.snapshot.selectedSchema).toEqual(updated); + expect(newField!).toBeDefined(); }); it('should update schema and selected schema when field removed', () => { diff --git a/src/Squidex/app/shared/state/schemas.state.ts b/src/Squidex/app/shared/state/schemas.state.ts index 68c3ea6d5..131fc1ad2 100644 --- a/src/Squidex/app/shared/state/schemas.state.ts +++ b/src/Squidex/app/shared/state/schemas.state.ts @@ -322,7 +322,7 @@ export class SchemasState extends State { function getField(x: SchemaDetailsDto, request: AddFieldDto, parent?: RootFieldDto): FieldDto { if (parent) { - return parent.nested.find(f => f.name === request.name)!; + return x.fields.find(f => f.fieldId === parent.fieldId)!.nested.find(f => f.name === request.name)!; } else { return x.fields.find(f => f.name === request.name)!; }