From 7c7cc96f04f26e515a0a0817c23b097790fa80b9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 14 May 2018 12:39:40 +0200 Subject: [PATCH 1/3] API for schema categories. --- Squidex.sln.DotSettings | 50 ------------------- .../Schemas/Commands/ChangeCategory.cs | 14 ++++++ .../Schemas/Guards/GuardSchema.cs | 5 ++ .../Schemas/ISchemaEntity.cs | 2 + .../Schemas/SchemaGrain.cs | 13 +++++ .../Schemas/State/SchemaState.cs | 8 +++ .../Schemas/SchemaCategoryChanged.cs | 17 +++++++ .../Schemas/Models/ChangeCategoryDto.cs | 25 ++++++++++ .../Controllers/Schemas/Models/SchemaDto.cs | 5 ++ .../Controllers/Schemas/SchemasController.cs | 22 ++++++++ .../shared/services/schemas.fields.spec.ts | 2 +- .../shared/services/schemas.service.spec.ts | 29 +++++++++-- .../app/shared/services/schemas.service.ts | 33 ++++++++++-- .../app/shared/state/schemas.state.spec.ts | 42 +++++++++++++--- src/Squidex/app/shared/state/schemas.state.ts | 49 +++++++++++++++++- .../Schemas/Guards/GuardSchemaTests.cs | 8 +++ .../Schemas/SchemaGrainTests.cs | 19 +++++++ 17 files changed, 275 insertions(+), 68 deletions(-) delete mode 100644 Squidex.sln.DotSettings create mode 100644 src/Squidex.Domain.Apps.Entities/Schemas/Commands/ChangeCategory.cs create mode 100644 src/Squidex.Domain.Apps.Events/Schemas/SchemaCategoryChanged.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Schemas/Models/ChangeCategoryDto.cs diff --git a/Squidex.sln.DotSettings b/Squidex.sln.DotSettings deleted file mode 100644 index ecdd59c86..000000000 --- a/Squidex.sln.DotSettings +++ /dev/null @@ -1,50 +0,0 @@ - - False - True - False - True - False - True - - False - True - - False - True - - - False - True - - False - True - - - False - True - - - True - - - - - - - - - <?xml version="1.0" encoding="utf-16"?><Profile name="Header"><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> - <?xml version="1.0" encoding="utf-16"?><Profile name="Namespaces"><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> - <?xml version="1.0" encoding="utf-16"?><Profile name="Typescript"><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs></Profile> - False - ========================================================================== - Squidex Headless CMS -========================================================================== - Copyright (c) Squidex UG (haftungsbeschraenkt) - All rights reserved. Licensed under the MIT license. -========================================================================== - - True - True - True - \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ChangeCategory.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ChangeCategory.cs new file mode 100644 index 000000000..c6eb7ff18 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ChangeCategory.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Entities.Schemas.Commands +{ + public sealed class ChangeCategory : SchemaCommand + { + public string Name { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index fb59048a1..81684f6b0 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -119,6 +119,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards Guard.NotNull(command, nameof(command)); } + public static void CanChangeCategory(Schema schema, ChangeCategory command) + { + Guard.NotNull(command, nameof(command)); + } + public static void CanDelete(Schema schema, DeleteSchema command) { Guard.NotNull(command, nameof(command)); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs index 8c341e76e..335a91e92 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs @@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas string Name { get; } + string Category { get; } + bool IsPublished { get; } bool IsDeleted { get; } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 95a1c7b02..62a30e545 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -161,6 +161,14 @@ namespace Squidex.Domain.Apps.Entities.Schemas ConfigureScripts(c); }); + case ChangeCategory changeCategory: + return UpdateAsync(changeCategory, c => + { + GuardSchema.CanChangeCategory(Snapshot.SchemaDef, c); + + ChangeCategory(c); + }); + case DeleteSchema deleteSchema: return UpdateAsync(deleteSchema, c => { @@ -253,6 +261,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured())); } + public void ChangeCategory(ChangeCategory command) + { + RaiseEvent(SimpleMapper.Map(command, new SchemaCategoryChanged())); + } + public void Delete(DeleteSchema command) { RaiseEvent(SimpleMapper.Map(command, new SchemaDeleted())); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs index 5f09910e6..256d42673 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs @@ -28,6 +28,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State [JsonProperty] public string Name { get; set; } + [JsonProperty] + public string Category { get; set; } + [JsonProperty] public int TotalFields { get; set; } = 0; @@ -126,6 +129,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State TotalFields++; } + protected void On(SchemaCategoryChanged @event, FieldRegistry registry) + { + Category = @event.Name; + } + protected void On(SchemaPublished @event, FieldRegistry registry) { SchemaDef = SchemaDef.Publish(); diff --git a/src/Squidex.Domain.Apps.Events/Schemas/SchemaCategoryChanged.cs b/src/Squidex.Domain.Apps.Events/Schemas/SchemaCategoryChanged.cs new file mode 100644 index 000000000..412ef928c --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Schemas/SchemaCategoryChanged.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Schemas +{ + [EventType(nameof(SchemaCategoryChanged))] + public sealed class SchemaCategoryChanged : SchemaEvent + { + public string Name { get; set; } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/ChangeCategoryDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/ChangeCategoryDto.cs new file mode 100644 index 000000000..29e497ec6 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/ChangeCategoryDto.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models +{ + public sealed class ChangeCategoryDto + { + /// + /// The name of the category. + /// + public string Name { get; set; } + + public ChangeCategory ToCommand() + { + return SimpleMapper.Map(this, new ChangeCategory()); + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index e36537ce7..0c0869ede 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -28,6 +28,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] public string Name { get; set; } + /// + /// The name of the category. + /// + public string Category { get; set; } + /// /// The schema properties. /// diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 1d2919a32..f7fc035c0 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -147,6 +147,28 @@ namespace Squidex.Areas.Api.Controllers.Schemas return NoContent(); } + /// + /// Update a schema category. + /// + /// The name of the app. + /// The name of the schema. + /// The schema object that needs to updated. + /// + /// 204 => Schema has been updated. + /// 400 => Schema properties are not valid. + /// 404 => Schema or app not found. + /// + [MustBeAppDeveloper] + [HttpPut] + [Route("apps/{app}/schemas/{name}/category")] + [ApiCosts(1)] + public async Task PutCategory(string app, string name, [FromBody] ChangeCategoryDto request) + { + await CommandBus.PublishAsync(request.ToCommand()); + + return NoContent(); + } + /// /// Update the scripts of a schema. /// diff --git a/src/Squidex/app/shared/services/schemas.fields.spec.ts b/src/Squidex/app/shared/services/schemas.fields.spec.ts index 765989151..323cac607 100644 --- a/src/Squidex/app/shared/services/schemas.fields.spec.ts +++ b/src/Squidex/app/shared/services/schemas.fields.spec.ts @@ -343,7 +343,7 @@ describe('StringField', () => { }); function createSchema(properties: SchemaPropertiesDto, index = 1, fields: FieldDto[]) { - return new SchemaDetailsDto('id' + index, 'schema' + index, properties, true, null!, null!, null!, null!, null!, fields); + return new SchemaDetailsDto('id' + index, 'schema' + index, '', properties, true, null!, null!, null!, null!, null!, fields); } function createField(properties: FieldPropertiesDto, index = 1, partitioning = 'languages') { diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 9ca10aa58..efbc37b83 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -21,6 +21,7 @@ import { SchemaPropertiesDto, SchemasService, UpdateFieldDto, + UpdateSchemaCategoryDto, UpdateSchemaDto, UpdateSchemaScriptsDto, Version @@ -70,6 +71,7 @@ describe('SchemasService', () => { { id: 'id1', name: 'name1', + category: 'category1', properties: { label: 'label1', hints: 'hints1' @@ -85,6 +87,7 @@ describe('SchemasService', () => { { id: 'id2', name: 'name2', + category: 'category2', properties: { label: 'label2', hints: 'hints2' @@ -100,11 +103,11 @@ describe('SchemasService', () => { ]); expect(schemas).toEqual([ - new SchemaDto('id1', 'name1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1', + new SchemaDto('id1', 'name1', 'category1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1', DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), new Version('11')), - new SchemaDto('id2', 'name2', new SchemaPropertiesDto('label2', 'hints2'), true, 'Created2', 'LastModifiedBy2', + new SchemaDto('id2', 'name2', 'category2', new SchemaPropertiesDto('label2', 'hints2'), true, 'Created2', 'LastModifiedBy2', DateTime.parseISO_UTC('2016-10-12T10:10'), DateTime.parseISO_UTC('2017-10-12T10:10'), new Version('22')) @@ -128,6 +131,7 @@ describe('SchemasService', () => { req.flush({ id: 'id1', name: 'name1', + category: 'category1', isPublished: true, created: '2016-12-12T10:10', createdBy: 'Created1', @@ -250,7 +254,7 @@ describe('SchemasService', () => { }); expect(schema).toEqual( - new SchemaDetailsDto('id1', 'name1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1', + new SchemaDetailsDto('id1', 'name1', 'category1', new SchemaPropertiesDto('label1', 'hints1'), true, 'Created1', 'LastModifiedBy1', DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'), new Version('2'), @@ -297,7 +301,7 @@ describe('SchemasService', () => { }); expect(schema).toEqual( - new SchemaDetailsDto('1', dto.name, new SchemaPropertiesDto(), false, user, user, now, now, new Version('2'), [])); + new SchemaDetailsDto('1', dto.name, '', new SchemaPropertiesDto(), false, user, user, now, now, new Version('2'), [])); })); it('should make post request to add field', @@ -342,7 +346,7 @@ describe('SchemasService', () => { const dto = new UpdateSchemaScriptsDto(); - schemasService.putSchemaScripts('my-app', 'my-schema', dto, version).subscribe(); + schemasService.putScripts('my-app', 'my-schema', dto, version).subscribe(); const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/scripts'); @@ -352,6 +356,21 @@ describe('SchemasService', () => { req.flush({}); })); + it('should make put request to update category', + inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { + + const dto = new UpdateSchemaCategoryDto(); + + schemasService.putCategory('my-app', 'my-schema', dto, version).subscribe(); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/category'); + + expect(req.request.method).toEqual('PUT'); + expect(req.request.headers.get('If-Match')).toBe(version.value); + + req.flush({}); + })); + it('should make put request to update field', inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index 0e154710c..a5bbeaeec 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -104,6 +104,7 @@ export class SchemaDto { constructor( public readonly id: string, public readonly name: string, + public readonly category: string, public readonly properties: SchemaPropertiesDto, public readonly isPublished: boolean, public readonly createdBy: string, @@ -118,7 +119,7 @@ export class SchemaDto { export class SchemaDetailsDto extends SchemaDto { public readonly listFields: FieldDto[]; - constructor(id: string, name: string, properties: SchemaPropertiesDto, isPublished: boolean, createdBy: string, lastModifiedBy: string, created: DateTime, lastModified: DateTime, version: Version, + constructor(id: string, name: string, category: string, properties: SchemaPropertiesDto, isPublished: boolean, createdBy: string, lastModifiedBy: string, created: DateTime, lastModified: DateTime, version: Version, public readonly fields: FieldDto[], public readonly scriptQuery?: string, public readonly scriptCreate?: string, @@ -126,7 +127,7 @@ export class SchemaDetailsDto extends SchemaDto { public readonly scriptDelete?: string, public readonly scriptChange?: string ) { - super(id, name, properties, isPublished, createdBy, lastModifiedBy, created, lastModified, version); + super(id, name, category, properties, isPublished, createdBy, lastModifiedBy, created, lastModified, version); this.listFields = this.fields.filter(x => x.properties.isListField); @@ -600,6 +601,13 @@ export class UpdateSchemaDto { } } +export class UpdateSchemaCategoryDto { + constructor( + public readonly category?: string + ) { + } +} + export class AddFieldDto { constructor( public readonly name: string, @@ -659,7 +667,9 @@ export class SchemasService { return new SchemaDto( item.id, - item.name, properties, + item.name, + item.category, + properties, item.isPublished, item.createdBy, item.lastModifiedBy, @@ -698,7 +708,9 @@ export class SchemasService { return new SchemaDetailsDto( body.id, - body.name, properties, + body.name, + body.category, + properties, body.isPublished, body.createdBy, body.lastModifiedBy, @@ -727,6 +739,7 @@ export class SchemasService { return new SchemaDetailsDto( body.id, dto.name, + '', dto.properties || new SchemaPropertiesDto(), false, user, @@ -781,7 +794,7 @@ export class SchemasService { .pretifyError('Failed to delete schema. Please reload.'); } - public putSchemaScripts(appName: string, schemaName: string, dto: UpdateSchemaScriptsDto, version: Version): Observable> { + public putScripts(appName: string, schemaName: string, dto: UpdateSchemaScriptsDto, version: Version): Observable> { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/scripts`); return HTTP.putVersioned(this.http, url, dto, version) @@ -831,6 +844,16 @@ export class SchemasService { .pretifyError('Failed to unpublish schema. Please reload.'); } + public putCategory(appName: string, schemaName: string, dto: UpdateSchemaCategoryDto, version: Version): Observable> { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/category`); + + return HTTP.putVersioned(this.http, url, dto, version) + .do(() => { + this.analytics.trackEvent('Schema', 'CategoryChanged', appName); + }) + .pretifyError('Failed to change category. Please reload.'); + } + public putField(appName: string, schemaName: string, fieldId: number, dto: UpdateFieldDto, version: Version): Observable> { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}`); diff --git a/src/Squidex/app/shared/state/schemas.state.spec.ts b/src/Squidex/app/shared/state/schemas.state.spec.ts index 8df92f2af..1a8df79c6 100644 --- a/src/Squidex/app/shared/state/schemas.state.spec.ts +++ b/src/Squidex/app/shared/state/schemas.state.spec.ts @@ -23,6 +23,7 @@ import { SchemaDto, SchemasService, UpdateFieldDto, + UpdateSchemaCategoryDto, UpdateSchemaDto, UpdateSchemaScriptsDto, Version, @@ -39,15 +40,15 @@ describe('SchemasState', () => { const newVersion = new Version('2'); const oldSchemas = [ - new SchemaDto('id1', 'name1', {}, false, creator, creator, creation, creation, version), - new SchemaDto('id2', 'name2', {}, true , creator, creator, creation, creation, version) + new SchemaDto('id1', 'name1', 'category1', {}, false, creator, creator, creation, creation, version), + new SchemaDto('id2', 'name2', 'category2', {}, true , creator, creator, creation, creation, version) ]; const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String')); const field2 = new FieldDto(2, '2', true, true, true, 'l', createProperties('Number')); const schema = - new SchemaDetailsDto('id2', 'name2', {}, true, + new SchemaDetailsDto('id2', 'name2', 'category2', {}, true, creator, creator, creation, creation, version, @@ -170,12 +171,26 @@ describe('SchemasState', () => { expectToBeModified(schema_1); }); + it('should change category and update user info when category changed', () => { + const category = 'my-new-category'; + + schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is(i => i.category === category), version)) + .returns(() => Observable.of(new Versioned(newVersion, {}))); + + schemasState.changeCategory(oldSchemas[0], category, modified).subscribe(); + + const schema_1 = schemasState.snapshot.schemas.at(0); + + expect(schema_1.category).toEqual(category); + expectToBeModified(schema_1); + }); + describe('with selection', () => { beforeEach(() => { schemasState.select(schema.name).subscribe(); }); - it('should unmark published and update user info when published selected schema', () => { + it('should nmark published and update user info when published selected schema', () => { schemasService.setup(x => x.publishSchema(app, schema.name, version)) .returns(() => Observable.of(new Versioned(newVersion, {}))); @@ -187,6 +202,21 @@ describe('SchemasState', () => { expectToBeModified(schema_1); }); + + it('should change category and update user info when category of selected schema changed', () => { + const category = 'my-new-category'; + + schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is(i => i.category === category), version)) + .returns(() => Observable.of(new Versioned(newVersion, {}))); + + schemasState.changeCategory(oldSchemas[0], category, modified).subscribe(); + + const schema_1 = schemasState.snapshot.schemas.at(0); + + expect(schema_1.category).toEqual(category); + expectToBeModified(schema_1); + }); + it('should update properties and update user info when updated', () => { const request = new UpdateSchemaDto('name2_label', 'name2_hints'); @@ -205,7 +235,7 @@ describe('SchemasState', () => { it('should update script properties and update user info when scripts configured', () => { const request = new UpdateSchemaScriptsDto('query', 'create', 'update', 'delete', 'change'); - schemasService.setup(x => x.putSchemaScripts(app, schema.name, It.isAny(), version)) + schemasService.setup(x => x.putScripts(app, schema.name, It.isAny(), version)) .returns(() => Observable.of(new Versioned(newVersion, {}))); schemasState.configureScripts(schema, request, modified).subscribe(); @@ -223,7 +253,7 @@ describe('SchemasState', () => { it('should add schema to snapshot when created', () => { const request = new CreateSchemaDto('newName'); - const result = new SchemaDetailsDto('id4', 'newName', {}, false, modifier, modifier, modified, modified, version, []); + const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, modifier, modifier, modified, modified, version, []); schemasService.setup(x => x.postSchema(app, request, modifier, modified)) .returns(() => Observable.of(result)); diff --git a/src/Squidex/app/shared/state/schemas.state.ts b/src/Squidex/app/shared/state/schemas.state.ts index ec1a9ba9f..bdef4175a 100644 --- a/src/Squidex/app/shared/state/schemas.state.ts +++ b/src/Squidex/app/shared/state/schemas.state.ts @@ -36,6 +36,7 @@ import { SchemaPropertiesDto, SchemasService, UpdateFieldDto, + UpdateSchemaCategoryDto, UpdateSchemaDto, UpdateSchemaScriptsDto } from './../services/schemas.service'; @@ -272,6 +273,14 @@ export class SchemasState extends State { .notify(this.dialogs); } + public changeCategory(schema: SchemaDto, name: string, now?: DateTime): Observable { + return this.schemasService.putCategory(this.appName, schema.name, new UpdateSchemaCategoryDto(name), schema.version) + .do(dto => { + this.replaceSchema(changeCategory(schema, name, this.user, dto.version, now)); + }) + .notify(this.dialogs); + } + public enableField(schema: SchemaDetailsDto, field: FieldDto, now?: DateTime): Observable { return this.schemasService.enableField(this.appName, schema.name, field.fieldId, schema.version) .do(dto => { @@ -337,7 +346,7 @@ export class SchemasState extends State { } public configureScripts(schema: SchemaDetailsDto, request: UpdateSchemaScriptsDto, now?: DateTime): Observable { - return this.schemasService.putSchemaScripts(this.appName, schema.name, request, schema.version) + return this.schemasService.putScripts(this.appName, schema.name, request, schema.version) .do(dto => { this.replaceSchema(configureScripts(schema, request, this.user, dto.version, now)); }) @@ -375,6 +384,7 @@ const setPublished = (schema: SchemaDto | SchemaDetailsDto, publish: boolean, us return new SchemaDetailsDto( schema.id, schema.name, + schema.category, schema.properties, publish, schema.createdBy, user, @@ -390,6 +400,7 @@ const setPublished = (schema: SchemaDto | SchemaDetailsDto, publish: boolean, us return new SchemaDto( schema.id, schema.name, + schema.category, schema.properties, publish, schema.createdBy, user, @@ -398,10 +409,41 @@ const setPublished = (schema: SchemaDto | SchemaDetailsDto, publish: boolean, us } }; +const changeCategory = (schema: SchemaDto | SchemaDetailsDto, category: string, user: string, version: Version, now?: DateTime) => { + if (Types.is(schema, SchemaDetailsDto)) { + return new SchemaDetailsDto( + schema.id, + schema.name, + category, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version, + schema.fields, + schema.scriptQuery, + schema.scriptCreate, + schema.scriptUpdate, + schema.scriptDelete, + schema.scriptChange); + } else { + return new SchemaDto( + schema.id, + schema.name, + category, + schema.properties, + schema.isPublished, + schema.createdBy, user, + schema.created, now || DateTime.now(), + version); + } +}; + const configureScripts = (schema: SchemaDetailsDto, scripts: UpdateSchemaScriptsDto, user: string, version: Version, now?: DateTime) => new SchemaDetailsDto( schema.id, schema.name, + schema.category, schema.properties, schema.isPublished, schema.createdBy, user, @@ -418,6 +460,7 @@ const updateProperties = (schema: SchemaDetailsDto, properties: SchemaProperties new SchemaDetailsDto( schema.id, schema.name, + schema.category, properties, schema.isPublished, schema.createdBy, user, @@ -434,6 +477,7 @@ const addField = (schema: SchemaDetailsDto, field: FieldDto, user: string, versi new SchemaDetailsDto( schema.id, schema.name, + schema.category, schema.properties, schema.isPublished, schema.createdBy, user, @@ -450,6 +494,7 @@ const updateField = (schema: SchemaDetailsDto, field: FieldDto, user: string, ve new SchemaDetailsDto( schema.id, schema.name, + schema.category, schema.properties, schema.isPublished, schema.createdBy, user, @@ -466,6 +511,7 @@ const replaceFields = (schema: SchemaDetailsDto, fields: FieldDto[], user: strin new SchemaDetailsDto( schema.id, schema.name, + schema.category, schema.properties, schema.isPublished, schema.createdBy, user, @@ -482,6 +528,7 @@ const removeField = (schema: SchemaDetailsDto, field: FieldDto, user: string, ve new SchemaDetailsDto( schema.id, schema.name, + schema.category, schema.properties, schema.isPublished, schema.createdBy, user, diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 1f2bd3233..7f723fa68 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -186,6 +186,14 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards GuardSchema.CanReorder(schema_0, command); } + [Fact] + public void CanChangeCategory_should_not_throw_exception() + { + var command = new ChangeCategory(); + + GuardSchema.CanChangeCategory(schema_0, command); + } + [Fact] public void CanDelete_should_not_throw_exception() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs index 2372d527e..f45e200c1 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs @@ -211,6 +211,25 @@ namespace Squidex.Domain.Apps.Entities.Schemas ); } + [Fact] + public async Task ChangeCategory_should_create_events_and_update_state() + { + var command = new ChangeCategory { Name = "my-category" }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(CreateCommand(command)); + + result.ShouldBeEquivalent(new EntitySavedResult(1)); + + Assert.Equal(command.Name, sut.Snapshot.Category); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new SchemaCategoryChanged { Name = command.Name }) + ); + } + [Fact] public async Task Delete_should_create_events_and_update_state() { From a874246bb3cbc6c1459941d0d1d2d0178b0a167a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 14 May 2018 15:58:33 +0200 Subject: [PATCH 2/3] UI for schema category. --- .../Schemas/Models/SchemaDetailsDto.cs | 5 + .../pages/schemas/schemas-page.component.html | 11 ++- .../pages/schemas/schemas-page.component.ts | 22 +---- .../pages/schemas/schemas-page.component.html | 31 ++---- .../pages/schemas/schemas-page.component.scss | 35 ++----- .../pages/schemas/schemas-page.component.ts | 39 +++++--- .../components/schema-category.component.html | 40 ++++++++ .../components/schema-category.component.scss | 85 ++++++++++++++++ .../components/schema-category.component.ts | 99 +++++++++++++++++++ src/Squidex/app/shared/declarations.ts | 1 + src/Squidex/app/shared/module.ts | 3 + .../app/shared/services/schemas.service.ts | 2 +- .../app/shared/state/schemas.state.spec.ts | 4 +- src/Squidex/app/shared/state/schemas.state.ts | 71 ++++++++++++- 14 files changed, 356 insertions(+), 92 deletions(-) create mode 100644 src/Squidex/app/shared/components/schema-category.component.html create mode 100644 src/Squidex/app/shared/components/schema-category.component.scss create mode 100644 src/Squidex/app/shared/components/schema-category.component.ts diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index 2acb18d4d..c61a19654 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -30,6 +30,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] public string Name { get; set; } + /// + /// The name of the category. + /// + public string Category { get; set; } + /// /// Indicates if the schema is published. /// diff --git a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html index 6c26e907a..589002432 100644 --- a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html +++ b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html @@ -16,11 +16,12 @@ - + + diff --git a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts index b85894f80..9070402df 100644 --- a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts +++ b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts @@ -8,11 +8,7 @@ import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { - AppsState, - SchemaDto, - SchemasState -} from '@app/shared'; +import { AppsState, SchemasState } from '@app/shared'; @Component({ selector: 'sqx-schemas-page', @@ -21,20 +17,10 @@ import { }) export class SchemasPageComponent implements OnInit { public schemasFilter = new FormControl(); - public schemasFiltered = - this.schemasState.publishedSchemas - .combineLatest(this.schemasFilter.valueChanges.startWith(''), - (schemas, query) => { - if (query && query.length > 0) { - return schemas.filter(t => t.name.indexOf(query) >= 0); - } else { - return schemas; - } - }); constructor( public readonly appsState: AppsState, - private readonly schemasState: SchemasState + public readonly schemasState: SchemasState ) { } @@ -42,8 +28,8 @@ export class SchemasPageComponent implements OnInit { this.schemasState.load().onErrorResumeNext().subscribe(); } - public trackBySchema(index: number, schema: SchemaDto) { - return schema.id; + public trackByCategory(index: number, category: string) { + return category; } } diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html index 1a8305bcc..96c98a83b 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html @@ -21,27 +21,16 @@ - + + + +
+ +
diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss index 15f173c4f..5a29b59c7 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss @@ -15,35 +15,16 @@ $button-size: calc(2.5rem - 2px); line-height: 2.5rem; } -.nav-link { - padding-top: .75rem; - padding-bottom: .75rem; -} - -.schema { - &-name { - @include truncate; - color: $color-dark-foreground; - } - - &-modified { - text-align: right; - width: auto; - white-space: nowrap; +.new-category-input { + & { + margin-top: 1rem; + background: 0; + border-width: 0; + border-bottom-width: 1px; padding-left: 0; } - &-user { - @include border-radius(1px); - @include truncate; - display: inline-block; - background: $color-dark2-control; - padding: .1rem .25rem; - font-size: .8rem; - font-weight: normal; - margin-left: 10px; - margin-bottom: 2px; - max-width: 100%; - vertical-align: middle; + &:focus { + @include box-shadow-none; } } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts index ab500d487..b381ea8ab 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts @@ -6,12 +6,13 @@ */ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { FormBuilder, FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { AppsState, + CreateCategoryForm, MessageBus, ModalView, SchemaDto, @@ -29,27 +30,19 @@ export class SchemasPageComponent implements OnDestroy, OnInit { private schemaCloningSubscription: Subscription; public addSchemaDialog = new ModalView(); + public addCategoryForm = new CreateCategoryForm(this.formBuilder); public schemasFilter = new FormControl(); - public schemasFiltered = - this.schemasState.schemas - .combineLatest(this.schemasFilter.valueChanges.startWith(''), - (schemas, query) => { - if (query && query.length > 0) { - return schemas.filter(t => t.name.indexOf(query) >= 0); - } else { - return schemas; - } - }); public import: any; constructor( public readonly appsState: AppsState, + public readonly schemasState: SchemasState, + private readonly formBuilder: FormBuilder, private readonly messageBus: MessageBus, private readonly route: ActivatedRoute, - private readonly router: Router, - private readonly schemasState: SchemasState + private readonly router: Router ) { } @@ -76,6 +69,22 @@ export class SchemasPageComponent implements OnDestroy, OnInit { this.schemasState.load().onErrorResumeNext().subscribe(); } + public removeCategory(name: string) { + this.schemasState.removeCategory(name); + } + + public addCategory() { + const value = this.addCategoryForm.submit(); + + if (value) { + try { + this.schemasState.addCategory(value.name); + } finally { + this.addCategoryForm.submitCompleted({}); + } + } + } + public onSchemaCreated(schema: SchemaDto) { this.router.navigate([schema.name], { relativeTo: this.route }); @@ -88,8 +97,8 @@ export class SchemasPageComponent implements OnDestroy, OnInit { this.addSchemaDialog.show(); } - public trackBySchema(index: number, schema: SchemaDto) { - return schema.id; + public trackByCategory(index: number, category: string) { + return category; } } diff --git a/src/Squidex/app/shared/components/schema-category.component.html b/src/Squidex/app/shared/components/schema-category.component.html new file mode 100644 index 000000000..6c989e85b --- /dev/null +++ b/src/Squidex/app/shared/components/schema-category.component.html @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/schema-category.component.scss b/src/Squidex/app/shared/components/schema-category.component.scss new file mode 100644 index 000000000..320cc3303 --- /dev/null +++ b/src/Squidex/app/shared/components/schema-category.component.scss @@ -0,0 +1,85 @@ +@import '_mixins'; +@import '_vars'; + +$drag-margin: -8px; + +h3 { + display: inline-block; +} + +.btn { + width: 2rem; +} + +.category { + margin-bottom: 1rem; +} + +.dnd-drag-start { + border: 0; +} + +.droppable { + & { + position: relative; + } + + &.dnd-drag-over, + &.dnd-drag-enter { + & { + border: 0; + } + + .drop-indicator { + display: block; + } + } + + .drop-indicator { + @include absolute($drag-margin, $drag-margin, $drag-margin, $drag-margin); + border: 2px dashed $color-dark-black; + background: none; + display: none; + pointer-events: none; + } +} + +.header { + margin-left: -1rem; +} + +.nav-link { + padding-top: .75rem; + padding-bottom: .75rem; +} + +.schema { + &-name { + @include truncate; + } + + &-name-accent { + color: $color-dark-foreground; + } + + &-modified { + text-align: right; + width: auto; + white-space: nowrap; + padding-left: 0; + } + + &-user { + @include border-radius(1px); + @include truncate; + display: inline-block; + background: $color-dark2-control; + padding: .1rem .25rem; + font-size: .8rem; + font-weight: normal; + margin-left: 10px; + margin-bottom: 2px; + max-width: 100%; + vertical-align: middle; + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/components/schema-category.component.ts b/src/Squidex/app/shared/components/schema-category.component.ts new file mode 100644 index 000000000..2a6c260e6 --- /dev/null +++ b/src/Squidex/app/shared/components/schema-category.component.ts @@ -0,0 +1,99 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; + +import { + fadeAnimation, + ImmutableArray, + LocalStoreService, + SchemaDetailsDto, + SchemaDto, + SchemasState, + Types +} from '@app/shared/internal'; + +@Component({ + selector: 'sqx-schema-category', + styleUrls: ['./schema-category.component.scss'], + templateUrl: './schema-category.component.html', + animations: [ + fadeAnimation + ] +}) +export class SchemaCategoryComponent implements OnInit, OnChanges { + @Output() + public removing = new EventEmitter(); + + @Input() + public name: string; + + @Input() + public isReadonly: boolean; + + @Input() + public schemasFilter: string; + + @Input() + public schemas: ImmutableArray; + + public displayName: string; + + public schemasFiltered: ImmutableArray; + public schemasForCategory: ImmutableArray; + + public isOpen = true; + + public allowDrop = (schema: any) => { + return (Types.is(schema, SchemaDto) || Types.is(schema, SchemaDetailsDto)) && !this.isSameCategory(schema); + } + + constructor( + private readonly localStore: LocalStoreService, + private readonly schemasState: SchemasState + ) { + } + + public ngOnInit() { + this.isOpen = this.localStore.get(`schema-category.${name}`) !== 'false'; + } + + public toggle() { + this.isOpen = !this.isOpen; + + this.localStore.set(`schema-category.${name}`, this.isOpen + ''); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes['schemas'] || changes['schemasFilter']) { + const query = this.schemasFilter; + + this.schemasForCategory = this.schemas.filter(x => this.isSameCategory(x)); + this.schemasFiltered = this.schemasForCategory.filter(x => !query || x.name.indexOf(query) >= 0); + } + + if (changes['name']) { + if (!this.name || this.name.length === 0) { + this.displayName = 'All Schemas'; + } else { + this.displayName = this.name; + } + } + } + + private isSameCategory(schema: SchemaDto): boolean { + return (!this.name && !schema.category) || schema.category === this.name; + } + + public changeCategory(schema: SchemaDto) { + this.schemasState.changeCategory(schema, this.name).onErrorResumeNext().subscribe(); + } + + public trackBySchema(index: number, schema: SchemaDto) { + return schema.id; + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index 7c157fdda..63d282884 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/src/Squidex/app/shared/declarations.ts @@ -17,5 +17,6 @@ export * from './components/language-selector.component'; export * from './components/markdown-editor.component'; export * from './components/pipes'; export * from './components/rich-editor.component'; +export * from './components/schema-category.component'; export * from './internal'; \ No newline at end of file diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index 18abf4ea0..a32ac6428 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -61,6 +61,7 @@ import { RuleEventsState, RulesService, RulesState, + SchemaCategoryComponent, SchemaMustExistGuard, SchemaMustExistPublishedGuard, SchemasService, @@ -99,6 +100,7 @@ import { HistoryListComponent, LanguageSelectorComponent, MarkdownEditorComponent, + SchemaCategoryComponent, UserDtoPicture, UserIdPicturePipe, UserNamePipe, @@ -122,6 +124,7 @@ import { LanguageSelectorComponent, MarkdownEditorComponent, RouterModule, + SchemaCategoryComponent, UserDtoPicture, UserIdPicturePipe, UserNamePipe, diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index a5bbeaeec..2b56c4a6b 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -603,7 +603,7 @@ export class UpdateSchemaDto { export class UpdateSchemaCategoryDto { constructor( - public readonly category?: string + public readonly name?: string ) { } } diff --git a/src/Squidex/app/shared/state/schemas.state.spec.ts b/src/Squidex/app/shared/state/schemas.state.spec.ts index 1a8df79c6..195e870e9 100644 --- a/src/Squidex/app/shared/state/schemas.state.spec.ts +++ b/src/Squidex/app/shared/state/schemas.state.spec.ts @@ -174,7 +174,7 @@ describe('SchemasState', () => { it('should change category and update user info when category changed', () => { const category = 'my-new-category'; - schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is(i => i.category === category), version)) + schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is(i => i.name === category), version)) .returns(() => Observable.of(new Versioned(newVersion, {}))); schemasState.changeCategory(oldSchemas[0], category, modified).subscribe(); @@ -206,7 +206,7 @@ describe('SchemasState', () => { it('should change category and update user info when category of selected schema changed', () => { const category = 'my-new-category'; - schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is(i => i.category === category), version)) + schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is(i => i.name === category), version)) .returns(() => Observable.of(new Versioned(newVersion, {}))); schemasState.changeCategory(oldSchemas[0], category, modified).subscribe(); diff --git a/src/Squidex/app/shared/state/schemas.state.ts b/src/Squidex/app/shared/state/schemas.state.ts index bdef4175a..3d4375596 100644 --- a/src/Squidex/app/shared/state/schemas.state.ts +++ b/src/Squidex/app/shared/state/schemas.state.ts @@ -43,6 +43,14 @@ import { const FALLBACK_NAME = 'my-schema'; +export class CreateCategoryForm extends Form { + constructor(formBuilder: FormBuilder) { + super(formBuilder.group({ + name: [''] + })); + } +} + export class CreateSchemaForm extends Form { public schemaName = this.form.controls['name'].valueChanges.map(n => n || FALLBACK_NAME) @@ -150,6 +158,8 @@ export class AddFieldForm extends Form { } interface Snapshot { + categories: { [name: string]: boolean }; + schemasApp?: string; schemas: ImmutableArray; @@ -164,6 +174,10 @@ export class SchemasState extends State { this.changes.map(x => x.selectedSchema) .distinctUntilChanged(); + public categories = + this.changes.map(x => ImmutableArray.of(Object.keys(x.categories)).sortByStringAsc(s => s)) + .distinctUntilChanged(); + public schemas = this.changes.map(x => x.schemas) .distinctUntilChanged(); @@ -186,7 +200,7 @@ export class SchemasState extends State { private readonly dialogs: DialogService, private readonly schemasService: SchemasService ) { - super({ schemas: ImmutableArray.of() }); + super({ schemas: ImmutableArray.empty(), categories: {} }); } public select(idOrName: string | null): Observable { @@ -220,7 +234,9 @@ export class SchemasState extends State { return this.next(s => { const schemas = ImmutableArray.of(dtos).sortByStringAsc(x => x.displayName); - return { ...s, schemas, schemasApp: this.appName, isLoaded: true }; + const categories = buildCategories(s.categories, schemas); + + return { ...s, schemas, schemasApp: this.appName, isLoaded: true, categories }; }); }) .notify(this.dialogs); @@ -250,6 +266,22 @@ export class SchemasState extends State { .notify(this.dialogs); } + public addCategory(name: string) { + this.next(s => { + const categories = addCategory(s.categories, name); + + return { ...s, categories: categories }; + }); + } + + public removeCategory(name: string) { + this.next(s => { + const categories = removeCategory(s.categories, name); + + return { ...s, categories: categories }; + }); + } + public addField(schema: SchemaDetailsDto, request: AddFieldDto, now?: DateTime): Observable { return this.schemasService.postField(this.appName, schema.name, request, schema.version) .do(dto => { @@ -366,7 +398,9 @@ export class SchemasState extends State { const schemas = s.schemas.replaceBy('id', schema).sortByStringAsc(x => x.displayName); const selectedSchema = s.selectedSchema && s.selectedSchema.id === schema.id ? schema : s.selectedSchema; - return { ...s, schemas, selectedSchema }; + const categories = buildCategories(s.categories, schemas); + + return { ...s, schemas, selectedSchema, categories }; }); } @@ -379,6 +413,37 @@ export class SchemasState extends State { } } +function buildCategories(categories: { [name: string]: boolean }, schemas: ImmutableArray) { + categories = { ...categories }; + + for (let category in categories) { + if (!categories[category]) { + delete categories[category]; + } + } + for (let schema of schemas.values) { + categories[schema.category || ''] = false; + } + + return categories; +} + +function addCategory(categories: { [name: string]: boolean }, category: string) { + categories = { ...categories }; + + categories[category] = true; + + return categories; +} + +function removeCategory(categories: { [name: string]: boolean }, category: string) { + categories = { ...categories }; + + delete categories[category]; + + return categories; +} + const setPublished = (schema: SchemaDto | SchemaDetailsDto, publish: boolean, user: string, version: Version, now?: DateTime) => { if (Types.is(schema, SchemaDetailsDto)) { return new SchemaDetailsDto( From 265b4c407ae1f75d03914ec7851d325747e79fbc Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 14 May 2018 16:05:37 +0200 Subject: [PATCH 3/3] More tests --- .../app/shared/state/schemas.state.spec.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Squidex/app/shared/state/schemas.state.spec.ts b/src/Squidex/app/shared/state/schemas.state.spec.ts index 195e870e9..05abe8a8f 100644 --- a/src/Squidex/app/shared/state/schemas.state.spec.ts +++ b/src/Squidex/app/shared/state/schemas.state.spec.ts @@ -91,6 +91,18 @@ describe('SchemasState', () => { it('should load schemas', () => { expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas); expect(schemasState.snapshot.isLoaded).toBeTruthy(); + expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false }); + + schemasService.verifyAll(); + }); + + it('should not remove custom category when loading schemas', () => { + schemasState.addCategory('category3'); + schemasState.load(true).subscribe(); + + expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas); + expect(schemasState.snapshot.isLoaded).toBeTruthy(); + expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true }); schemasService.verifyAll(); }); @@ -101,6 +113,18 @@ describe('SchemasState', () => { dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once()); }); + it('should add category', () => { + schemasState.addCategory('category3'); + + 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 }); + }); + it('should return schema on select and reload when already loaded', () => { schemasState.select('name2').subscribe(); schemasState.select('name2').subscribe();