From 777c0b0716882eaee372a1460440179e9acc569d Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 11 Aug 2017 22:42:17 +0200 Subject: [PATCH] 1) Fields locked. 2) Badges improved. --- src/Squidex.Domain.Apps.Core/Schemas/Field.cs | 38 +++++++++++- .../Schemas/Field_Generic.cs | 2 +- .../Schemas/Json/JsonFieldModel.cs | 3 + .../Schemas/Json/SchemaJsonSerializer.cs | 6 ++ .../Schemas/Schema.cs | 10 +++ .../Schemas/FieldLocked.cs | 17 ++++++ .../Schemas/SchemaCreatedField.cs | 2 + .../Schemas/Utils/SchemaEventDispatcher.cs | 10 +++ .../MongoSchemaRepository_EventHandling.cs | 13 ++-- .../Schemas/SchemaHistoryEventsCreator.cs | 13 ++-- .../Schemas/Commands/CreateSchemaField.cs | 2 + .../Schemas/Commands/LockField.cs | 14 +++++ .../Schemas/SchemaCommandMiddleware.cs | 19 +++--- .../Schemas/SchemaDomainObject.cs | 16 +++++ .../Schemas/Models/CreateSchemaFieldDto.cs | 5 ++ .../Api/Schemas/Models/FieldDto.cs | 5 ++ .../Api/Schemas/SchemaFieldsController.cs | 34 ++++++++++- .../schemas/pages/schema/field.component.html | 48 +++++++++++---- .../schemas/pages/schema/field.component.scss | 11 ++++ .../schemas/pages/schema/field.component.ts | 26 ++++++++ .../pages/schema/schema-page.component.html | 1 + .../pages/schema/schema-page.component.ts | 6 +- .../pages/webhook-events-page.component.html | 4 +- .../pages/webhook-events-page.component.scss | 30 --------- .../pages/webhook-events-page.component.ts | 12 ++++ .../shared/services/schemas.fields.spec.ts | 9 ++- .../shared/services/schemas.service.spec.ts | 61 +++++++++++++------ .../app/shared/services/schemas.service.ts | 24 ++++++-- .../pages/internal/apps-menu.component.html | 2 +- .../pages/internal/apps-menu.component.scss | 8 --- src/Squidex/app/theme/_bootstrap-vars.scss | 29 +++++---- src/Squidex/app/theme/_bootstrap.scss | 48 +++++++++++++++ src/Squidex/app/theme/_vars.scss | 22 +++++-- .../Schemas/Json/JsonSerializerTests.cs | 3 + .../Schemas/SchemaTests.cs | 46 ++++++++++++++ .../Schemas/SchemaCommandHandlerTests.cs | 14 +++++ .../Schemas/SchemaDomainObjectTests.cs | 48 +++++++++++++++ 37 files changed, 541 insertions(+), 120 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Events/Schemas/FieldLocked.cs create mode 100644 src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs diff --git a/src/Squidex.Domain.Apps.Core/Schemas/Field.cs b/src/Squidex.Domain.Apps.Core/Schemas/Field.cs index b70304613..8b46a624b 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/Field.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/Field.cs @@ -28,6 +28,7 @@ namespace Squidex.Domain.Apps.Core.Schemas private string fieldName; private bool isDisabled; private bool isHidden; + private bool isLocked; public long Id { @@ -39,6 +40,11 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fieldName; } } + public bool IsLocked + { + get { return isLocked; } + } + public bool IsHidden { get { return isHidden; } @@ -75,10 +81,15 @@ namespace Squidex.Domain.Apps.Core.Schemas validators = new Lazy>(() => new List(CreateValidators())); } - public abstract Field Update(FieldProperties newProperties); + protected abstract Field UpdateInternal(FieldProperties newProperties); public abstract object ConvertValue(JToken value); + public Field Lock() + { + return Clone(clone => clone.isLocked = true); + } + public Field Hide() { return Clone(clone => clone.isHidden = true); @@ -99,7 +110,30 @@ namespace Squidex.Domain.Apps.Core.Schemas return Clone(clone => clone.isDisabled = false); } + public Field Update(FieldProperties newProperties) + { + ThrowIfLocked(); + + return UpdateInternal(newProperties); + } + public Field Rename(string newName) + { + ThrowIfLocked(); + ThrowIfSameName(newName); + + return Clone(clone => clone.fieldName = newName); + } + + private void ThrowIfLocked() + { + if (isLocked) + { + throw new DomainException($"Field {fieldId} is locked."); + } + } + + private void ThrowIfSameName(string newName) { if (!newName.IsSlug()) { @@ -107,8 +141,6 @@ namespace Squidex.Domain.Apps.Core.Schemas throw new ValidationException($"Cannot rename the field '{fieldName}' ({fieldId})", error); } - - return Clone(clone => clone.fieldName = newName); } public void AddToEdmType(EdmStructuredType edmType, PartitionResolver partitionResolver, string schemaName, Func typeResolver) diff --git a/src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs b/src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs index 3b75ac847..7749529ad 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Core.Schemas this.properties = ValidateProperties(properties); } - public override Field Update(FieldProperties newProperties) + protected override Field UpdateInternal(FieldProperties newProperties) { var typedProperties = ValidateProperties(newProperties); diff --git a/src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs b/src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs index 424bdbfaf..51b103012 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs @@ -18,6 +18,9 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json [JsonProperty] public bool IsHidden { get; set; } + [JsonProperty] + public bool IsLocked { get; set; } + [JsonProperty] public bool IsDisabled { get; set; } diff --git a/src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaJsonSerializer.cs b/src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaJsonSerializer.cs index 36a6bb102..1961a70da 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaJsonSerializer.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaJsonSerializer.cs @@ -42,6 +42,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json Id = x.Id, Name = x.Name, IsHidden = x.IsHidden, + IsLocked = x.IsLocked, IsDisabled = x.IsDisabled, Partitioning = x.Paritioning.Key, Properties = x.RawProperties @@ -66,6 +67,11 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json field = field.Disable(); } + if (fieldModel.IsLocked) + { + field = field.Lock(); + } + if (fieldModel.IsHidden) { field = field.Hide(); diff --git a/src/Squidex.Domain.Apps.Core/Schemas/Schema.cs b/src/Squidex.Domain.Apps.Core/Schemas/Schema.cs index e4fc03f5f..d9e9605b5 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/Schema.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/Schema.cs @@ -101,6 +101,11 @@ namespace Squidex.Domain.Apps.Core.Schemas return UpdateField(fieldId, field => field.Update(newProperties)); } + public Schema LockField(long fieldId) + { + return UpdateField(fieldId, field => field.Lock()); + } + public Schema DisableField(long fieldId) { return UpdateField(fieldId, field => field.Disable()); @@ -128,6 +133,11 @@ namespace Squidex.Domain.Apps.Core.Schemas public Schema DeleteField(long fieldId) { + if (fieldsById.TryGetValue(fieldId, out var field) && field.IsLocked) + { + throw new DomainException($"Field {fieldId} is locked."); + } + return new Schema(name, isPublished, properties, fields.Where(x => x.Id != fieldId).ToImmutableList()); } diff --git a/src/Squidex.Domain.Apps.Events/Schemas/FieldLocked.cs b/src/Squidex.Domain.Apps.Events/Schemas/FieldLocked.cs new file mode 100644 index 000000000..d9b7f2675 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Schemas/FieldLocked.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// FieldLocked.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Events.Schemas +{ + [TypeName("FieldLockedEvent")] + public class FieldLocked : FieldEvent + { + } +} diff --git a/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs b/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs index 29a754ba0..6487cf4c3 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs @@ -18,6 +18,8 @@ namespace Squidex.Domain.Apps.Events.Schemas public bool IsHidden { get; set; } + public bool IsLocked { get; set; } + public bool IsDisabled { get; set; } public FieldProperties Properties { get; set; } diff --git a/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs index 72634e059..8b93d0a9d 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs @@ -44,6 +44,11 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils field = field.Disable(); } + if (eventField.IsLocked) + { + field = field.Lock(); + } + schema = schema.AddOrUpdateField(field); fieldId++; @@ -68,6 +73,11 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils return schema.UpdateField(@event.FieldId.Id, @event.Properties); } + public static Schema Dispatch(FieldLocked @event, Schema schema) + { + return schema.LockField(@event.FieldId.Id); + } + public static Schema Dispatch(FieldHidden @event, Schema schema) { return schema.HideField(@event.FieldId.Id); diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs index c042c162d..8770945bb 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs @@ -48,22 +48,27 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Schemas return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); } - protected Task On(FieldDisabled @event, EnvelopeHeaders headers) + protected Task On(FieldLocked @event, EnvelopeHeaders headers) { return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); } - protected Task On(FieldEnabled @event, EnvelopeHeaders headers) + protected Task On(FieldHidden @event, EnvelopeHeaders headers) { return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); } - protected Task On(FieldHidden @event, EnvelopeHeaders headers) + protected Task On(FieldShown @event, EnvelopeHeaders headers) { return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); } - protected Task On(FieldShown @event, EnvelopeHeaders headers) + protected Task On(FieldDisabled @event, EnvelopeHeaders headers) + { + return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); + } + + protected Task On(FieldEnabled @event, EnvelopeHeaders headers) { return UpdateSchema(@event, headers, s => SchemaEventDispatcher.Dispatch(@event, s)); } diff --git a/src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs index 8c50c8feb..0dd93b49b 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs @@ -46,11 +46,8 @@ namespace Squidex.Domain.Apps.Read.Schemas AddEventMessage( "deleted field {[Field]} from schema {[Name]}"); - AddEventMessage( - "disabled field {[Field]} of schema {[Name]}"); - - AddEventMessage( - "disabled field {[Field]} of schema {[Name]}"); + AddEventMessage( + "has locked field {[Field]} of schema {[Name]}"); AddEventMessage( "has hidden field {[Field]} of schema {[Name]}"); @@ -58,6 +55,12 @@ namespace Squidex.Domain.Apps.Read.Schemas AddEventMessage( "has shown field {[Field]} of schema {[Name]}"); + AddEventMessage( + "disabled field {[Field]} of schema {[Name]}"); + + AddEventMessage( + "disabled field {[Field]} of schema {[Name]}"); + AddEventMessage( "has updated field {[Field]} of schema {[Name]}"); diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs index a9c8fb7f7..b178177d1 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs @@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Write.Schemas.Commands public bool IsHidden { get; set; } + public bool IsLocked { get; set; } + public bool IsDisabled { get; set; } public FieldProperties Properties { get; set; } diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs new file mode 100644 index 000000000..9b456e93b --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// LockField.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Domain.Apps.Write.Schemas.Commands +{ + public class LockField : FieldCommand + { + } +} diff --git a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs index 9a5965f8a..823e4b5bf 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs @@ -80,14 +80,9 @@ namespace Squidex.Domain.Apps.Write.Schemas return handler.UpdateAsync(context, s => s.DeleteField(command)); } - protected Task On(DisableField command, CommandContext context) + protected Task On(LockField command, CommandContext context) { - return handler.UpdateAsync(context, s => s.DisableField(command)); - } - - protected Task On(EnableField command, CommandContext context) - { - return handler.UpdateAsync(context, s => s.EnableField(command)); + return handler.UpdateAsync(context, s => s.LockField(command)); } protected Task On(HideField command, CommandContext context) @@ -100,6 +95,16 @@ namespace Squidex.Domain.Apps.Write.Schemas return handler.UpdateAsync(context, s => s.ShowField(command)); } + protected Task On(DisableField command, CommandContext context) + { + return handler.UpdateAsync(context, s => s.DisableField(command)); + } + + protected Task On(EnableField command, CommandContext context) + { + return handler.UpdateAsync(context, s => s.EnableField(command)); + } + protected Task On(ReorderFields command, CommandContext context) { return handler.UpdateAsync(context, s => s.Reorder(command)); diff --git a/src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs b/src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs index 6234d94d4..371096fb7 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs @@ -65,6 +65,11 @@ namespace Squidex.Domain.Apps.Write.Schemas schema = SchemaEventDispatcher.Dispatch(@event, schema); } + protected void On(FieldLocked @event) + { + schema = SchemaEventDispatcher.Dispatch(@event, schema); + } + protected void On(FieldHidden @event) { schema = SchemaEventDispatcher.Dispatch(@event, schema); @@ -217,6 +222,17 @@ namespace Squidex.Domain.Apps.Write.Schemas return this; } + public SchemaDomainObject LockField(LockField command) + { + Guard.NotNull(command, nameof(command)); + + VerifyCreatedAndNotDeleted(); + + RaiseEvent(command, new FieldLocked()); + + return this; + } + public SchemaDomainObject HideField(HideField command) { Guard.NotNull(command, nameof(command)); diff --git a/src/Squidex/Controllers/Api/Schemas/Models/CreateSchemaFieldDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/CreateSchemaFieldDto.cs index 521948698..b42db8444 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/CreateSchemaFieldDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/CreateSchemaFieldDto.cs @@ -24,6 +24,11 @@ namespace Squidex.Controllers.Api.Schemas.Models /// public bool IsHidden { get; set; } + /// + /// Defines if the field is locked. + /// + public bool IsLocked { get; set; } + /// /// Defines if the field is disabled. /// diff --git a/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs index 7b1c97bd7..8e8c95f14 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs @@ -29,6 +29,11 @@ namespace Squidex.Controllers.Api.Schemas.Models /// public bool IsHidden { get; set; } + /// + /// Defines if the field is locked. + /// + public bool IsLocked { get; set; } + /// /// Defines if the field is disabled. /// diff --git a/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs b/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs index 79a7004c0..1fb5ba493 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs @@ -98,7 +98,7 @@ namespace Squidex.Controllers.Api.Schemas /// The field object that needs to be added to the schema. /// /// 204 => Schema field updated. - /// 400 => Schema field properties not valid. + /// 400 => Schema field properties not valid or field is locked. /// 404 => Schema, field or app not found. /// [HttpPut] @@ -115,6 +115,33 @@ namespace Squidex.Controllers.Api.Schemas return NoContent(); } + /// + /// Lock a schema field. + /// + /// The name of the app. + /// The name of the schema. + /// The id of the field to lock. + /// + /// 204 => Schema field shown. + /// 400 => Schema field already locked. + /// 404 => Schema, field or app not found. + /// + /// + /// A hidden field is not part of the API response, but can still be edited in the portal. + /// + [HttpPut] + [Route("apps/{app}/schemas/{name}/fields/{id:long}/lock/")] + [ProducesResponseType(typeof(ErrorDto), 400)] + [ApiCosts(1)] + public async Task LockField(string app, string name, long id) + { + var command = new LockField { FieldId = id }; + + await CommandBus.PublishAsync(command); + + return NoContent(); + } + /// /// Hide a schema field. /// @@ -127,7 +154,7 @@ namespace Squidex.Controllers.Api.Schemas /// 404 => Schema, field or app not found. /// /// - /// A hidden field is not part of the API response, but can still be edited in the portal. + /// A locked field cannot be edited or deleted. /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/hide/")] @@ -147,7 +174,7 @@ namespace Squidex.Controllers.Api.Schemas /// /// The name of the app. /// The name of the schema. - /// The id of the field to shows. + /// The id of the field to show. /// /// 204 => Schema field shown. /// 400 => Schema field already visible. @@ -233,6 +260,7 @@ namespace Squidex.Controllers.Api.Schemas /// The id of the field to disable. /// /// 204 => Schema field deleted. + /// 400 => Field is locked. /// 404 => Schema, field or app not found. /// [HttpDelete] diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index 0231c6e8d..647444d0b 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -9,13 +9,14 @@ localizable -
+
- Enabled - Disabled + Locked + Enabled + Disabled
-
+
-
+
- - + +
@@ -182,4 +186,24 @@
+
+ + \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.scss b/src/Squidex/app/features/schemas/pages/schema/field.component.scss index f5a781b35..f58da9790 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.scss +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.scss @@ -7,6 +7,17 @@ $field-header: #e7ebef; cursor: default; } +.col { + &-tags, + &-options { + white-space: nowrap; + } + + &-options { + max-width: 140px; + } +} + .field { &-icon { margin-right: 1rem; 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 3fb298628..9705f8c43 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.ts @@ -31,6 +31,9 @@ export class FieldComponent implements OnInit { @Input() public schemas: SchemaDto[]; + @Output() + public locking = new EventEmitter(); + @Output() public hiding = new EventEmitter(); @@ -50,6 +53,7 @@ export class FieldComponent implements OnInit { public deleting = new EventEmitter(); public dropdown = new ModalView(false, true); + public lockDialog = new ModalView(false, true); public isEditing = false; public selectedTab = 0; @@ -104,6 +108,7 @@ export class FieldComponent implements OnInit { new FieldDto( this.field.fieldId, this.field.name, + this.field.isLocked, this.field.isHidden, this.field.isHidden, this.field.partitioning, @@ -113,6 +118,23 @@ export class FieldComponent implements OnInit { } } + public confirmLock() { + this.lockDialog.hide(); + this.emitLocking(this.field); + } + + public cancelLock() { + this.lockDialog.hide(); + } + + public askLock() { + this.lockDialog.show(); + } + + private emitLocking(field: FieldDto) { + this.locking.emit(field); + } + private emitSaving(field: FieldDto) { this.saving.emit(field); } @@ -121,6 +143,10 @@ export class FieldComponent implements OnInit { this.editFormSubmitted = false; this.editForm.reset(this.field.properties); + if (this.field.isLocked) { + this.editForm.disable(); + } + this.isEditing = false; } } diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html index ab697e5fa..40720ccb9 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -46,6 +46,7 @@ (disabling)="disableField(field)" (deleting)="deleteField(field)" (enabling)="enableField(field)" + (locking)="lockField(field)" (showing)="showField(field)" (hiding)="hideField(field)" (saving)="saveField($event)"> 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 ead1c368b..9cf1c4ab6 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 @@ -145,11 +145,11 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit { }); } - public showField(field: FieldDto) { + public lockField(field: FieldDto) { this.appNameOnce() - .switchMap(app => this.schemasService.showField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) + .switchMap(app => this.schemasService.lockField(app, this.schema.name, field.fieldId, this.schema.version)).retry(2) .subscribe(() => { - this.updateSchema(this.schema.updateField(field.show(), this.authService.user.token)); + this.updateSchema(this.schema.updateField(field.lock(), this.authService.user.token)); }, error => { this.notifyError(error); }); diff --git a/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.html b/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.html index 8b94da74c..55fca4595 100644 --- a/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.html +++ b/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.html @@ -51,7 +51,7 @@ - {{event.jobResult}} + {{event.jobResult}} {{event.eventName}} @@ -76,7 +76,7 @@
- {{event.result}} + {{event.result}}
Attempts: {{event.numCalls}} diff --git a/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss b/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss index de5846d30..1d99250e8 100644 --- a/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss +++ b/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.scss @@ -5,36 +5,6 @@ h3 { margin-bottom: 1rem; } -.badge { - & { - @include border-radius(12px); - font-size: .9rem; - font-weight: normal; - line-height: 1.5rem; - padding: 0 .5rem; - } - - &-small { - font-size: .8rem; - } - - &-pending { - background: $color-border; - } - - &-success { - background: $color-badge-success; - } - - &-retry { - background: $color-badge-warning; - } - - &-failed { - background: $color-badge-error; - } -} - .event { &-stats { font-size: .8rem; diff --git a/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts b/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts index 0b60a10f1..98aa5c21c 100644 --- a/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts +++ b/src/Squidex/app/features/webhooks/pages/webhook-events-page.component.ts @@ -78,5 +78,17 @@ export class WebhookEventsPageComponent extends AppComponentBase implements OnIn this.load(); } + + public getBadgeClass(status: string) { + if (status === 'Retry') { + return 'warning'; + } else if (status === 'Failed') { + return 'danger'; + } else if (status === 'Pending') { + return 'default'; + } else { + return status.toLowerCase(); + } + } } diff --git a/src/Squidex/app/shared/services/schemas.fields.spec.ts b/src/Squidex/app/shared/services/schemas.fields.spec.ts index 5c136cc3c..45965263b 100644 --- a/src/Squidex/app/shared/services/schemas.fields.spec.ts +++ b/src/Squidex/app/shared/services/schemas.fields.spec.ts @@ -20,6 +20,13 @@ import { } from './../'; describe('FieldDto', () => { + it('should update isLocked property when locking', () => { + const field_1 = createField(createProperties('String')); + const field_2 = field_1.lock(); + + expect(field_2.isLocked).toBeTruthy(); + }); + it('should update isHidden property when hiding', () => { const field_1 = createField(createProperties('String')); const field_2 = field_1.hide(); @@ -211,5 +218,5 @@ describe('NumberField', () => { }); function createField(properties: FieldPropertiesDto) { - return new FieldDto(1, 'field1', false, false, 'languages', properties); + return new FieldDto(1, 'field1', false, false, false, 'languages', properties); } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 61c9e4173..e3e1ae7ec 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -103,8 +103,8 @@ describe('SchemaDetailsDto', () => { }); it('should update fields property and user info when adding field', () => { - const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); - const field2 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); + const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String')); + const field2 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Number')); const now = DateTime.now(); @@ -117,8 +117,8 @@ describe('SchemaDetailsDto', () => { }); it('should update fields property and user info when removing field', () => { - const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); - const field2 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); + const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String')); + const field2 = new FieldDto(2, '2',false, false, false, 'l', createProperties('Number')); const now = DateTime.now(); @@ -131,8 +131,8 @@ describe('SchemaDetailsDto', () => { }); it('should update fields property and user info when replacing fields', () => { - const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); - const field2 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); + const field1 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String')); + const field2 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Number')); const now = DateTime.now(); @@ -145,16 +145,16 @@ describe('SchemaDetailsDto', () => { }); it('should update fields property and user info when updating field', () => { - const field1 = new FieldDto(1, '1', false, false, 'l', createProperties('String')); - const field2_1 = new FieldDto(2, '2', false, false, 'l', createProperties('Number')); - const field2_2 = new FieldDto(2, '2', false, false, 'l', createProperties('Boolean')); + const field1_0 = new FieldDto(1, '1', false, false, false, 'l', createProperties('String')); + const field2_1 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Number')); + const field2_2 = new FieldDto(2, '2', false, false, false, 'l', createProperties('Boolean')); const now = DateTime.now(); - const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1, field2_1]); + const schema_1 = new SchemaDetailsDto('1', 'name', properties, false, 'other', 'other', DateTime.now(), DateTime.now(), null, [field1_0, field2_1]); const schema_2 = schema_1.updateField(field2_2, 'me', now); - expect(schema_2.fields).toEqual([field1, field2_2]); + expect(schema_2.fields).toEqual([field1_0, field2_2]); expect(schema_2.lastModified).toEqual(now); expect(schema_2.lastModifiedBy).toEqual('me'); }); @@ -276,6 +276,7 @@ describe('SchemasService', () => { { fieldId: 1, name: 'field1', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -286,6 +287,7 @@ describe('SchemasService', () => { { fieldId: 2, name: 'field2', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -296,6 +298,7 @@ describe('SchemasService', () => { { fieldId: 3, name: 'field3', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -306,6 +309,7 @@ describe('SchemasService', () => { { fieldId: 4, name: 'field4', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -316,6 +320,7 @@ describe('SchemasService', () => { { fieldId: 5, name: 'field5', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -326,6 +331,7 @@ describe('SchemasService', () => { { fieldId: 6, name: 'field6', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -336,6 +342,7 @@ describe('SchemasService', () => { { fieldId: 7, name: 'field7', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -346,6 +353,7 @@ describe('SchemasService', () => { { fieldId: 8, name: 'field8', + isLocked: true, isHidden: true, isDisabled: true, partitioning: 'language', @@ -362,14 +370,14 @@ describe('SchemasService', () => { DateTime.parseISO_UTC('2017-12-12T10:10'), new Version('11'), [ - new FieldDto(1, 'field1', true, true, 'language', createProperties('Number')), - new FieldDto(2, 'field2', true, true, 'language', createProperties('String')), - new FieldDto(3, 'field3', true, true, 'language', createProperties('Boolean')), - new FieldDto(4, 'field4', true, true, 'language', createProperties('DateTime')), - new FieldDto(5, 'field5', true, true, 'language', createProperties('Json')), - new FieldDto(6, 'field6', true, true, 'language', createProperties('Geolocation')), - new FieldDto(7, 'field7', true, true, 'language', createProperties('Assets')), - new FieldDto(8, 'field8', true, true, 'language', createProperties('References')) + new FieldDto(1, 'field1', true, true, true, 'language', createProperties('Number')), + new FieldDto(2, 'field2', true, true, true, 'language', createProperties('String')), + new FieldDto(3, 'field3', true, true, true, 'language', createProperties('Boolean')), + new FieldDto(4, 'field4', true, true, true, 'language', createProperties('DateTime')), + new FieldDto(5, 'field5', true, true, true, 'language', createProperties('Json')), + new FieldDto(6, 'field6', true, true, true, 'language', createProperties('Geolocation')), + new FieldDto(7, 'field7', true, true, true, 'language', createProperties('Assets')), + new FieldDto(8, 'field8', true, true, true, 'language', createProperties('References')) ])); })); @@ -437,7 +445,7 @@ describe('SchemasService', () => { req.flush({ id: 123 }); expect(field).toEqual( - new FieldDto(123, dto.name, false, false, dto.partitioning, dto.properties)); + new FieldDto(123, dto.name, false, false, false, dto.partitioning, dto.properties)); })); it('should make put request to update schema', @@ -537,6 +545,19 @@ describe('SchemasService', () => { req.flush({}); })); + it('should make put request to lock field', + inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { + + schemasService.lockField('my-app', 'my-schema', 1, version).subscribe(); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/fields/1/lock'); + + expect(req.request.method).toEqual('PUT'); + expect(req.request.headers.get('If-Match')).toBe(version.value); + + req.flush({}); + })); + it('should make put request to show 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 b456ed886..88eb82ad9 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -215,6 +215,7 @@ export class FieldDto { constructor( public readonly fieldId: number, public readonly name: string, + public readonly isLocked: boolean, public readonly isHidden: boolean, public readonly isDisabled: boolean, public readonly partitioning: string, @@ -222,24 +223,28 @@ export class FieldDto { ) { } + public lock(): FieldDto { + return new FieldDto(this.fieldId, this.name, true, this.isHidden, this.isDisabled, this.partitioning, this.properties); + } + public show(): FieldDto { - return new FieldDto(this.fieldId, this.name, false, this.isDisabled, this.partitioning, this.properties); + return new FieldDto(this.fieldId, this.name, this.isLocked, false, this.isDisabled, this.partitioning, this.properties); } public hide(): FieldDto { - return new FieldDto(this.fieldId, this.name, true, this.isDisabled, this.partitioning, this.properties); + return new FieldDto(this.fieldId, this.name, this.isLocked, true, this.isDisabled, this.partitioning, this.properties); } public enable(): FieldDto { - return new FieldDto(this.fieldId, this.name, this.isHidden, false, this.partitioning, this.properties); + return new FieldDto(this.fieldId, this.name, this.isLocked, this.isHidden, false, this.partitioning, this.properties); } public disable(): FieldDto { - return new FieldDto(this.fieldId, this.name, this.isHidden, true, this.partitioning, this.properties); + return new FieldDto(this.fieldId, this.name, this.isLocked, this.isHidden, true, this.partitioning, this.properties); } public update(properties: FieldPropertiesDto): FieldDto { - return new FieldDto(this.fieldId, this.name, this.isHidden, this.isDisabled, this.partitioning, properties); + return new FieldDto(this.fieldId, this.name, this.isLocked, this.isHidden, this.isDisabled, this.partitioning, properties); } public formatValue(value: any): string { @@ -658,6 +663,7 @@ export class SchemasService { return new FieldDto( item.fieldId, item.name, + item.isLocked, item.isHidden, item.isDisabled, item.partitioning, @@ -727,6 +733,7 @@ export class SchemasService { dto.name, false, false, + false, dto.partitioning, dto.properties); }) @@ -792,6 +799,13 @@ export class SchemasService { .pretifyError('Failed to disable field. Please reload.'); } + public lockField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/lock`); + + return HTTP.putVersioned(this.http, url, {}, version) + .pretifyError('Failed to lock field. Please reload.'); + } + public showField(appName: string, schemaName: string, fieldId: number, version?: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/fields/${fieldId}/show`); diff --git a/src/Squidex/app/shell/pages/internal/apps-menu.component.html b/src/Squidex/app/shell/pages/internal/apps-menu.component.html index efa40fd9b..2bcfec3e0 100644 --- a/src/Squidex/app/shell/pages/internal/apps-menu.component.html +++ b/src/Squidex/app/shell/pages/internal/apps-menu.component.html @@ -5,7 +5,7 @@