From be7c7a62185071aac490e48ea2aa33cce245b618 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 19 Feb 2022 17:05:14 +0100 Subject: [PATCH] Optin to GraphQL enum. (#849) --- backend/i18n/frontend_en.json | 2 + backend/i18n/frontend_it.json | 2 + backend/i18n/frontend_nl.json | 2 + backend/i18n/frontend_zh.json | 2 + backend/i18n/source/frontend_en.json | 2 + .../Schemas/StringFieldProperties.cs | 2 + .../Schemas/TagsFieldProperties.cs | 2 + .../Contents/GraphQL/Types/Builder.cs | 6 +- .../GraphQL/Types/Contents/FieldEnumType.cs | 25 ++++---- .../Types/Contents/FieldInputVisitor.cs | 29 +++++---- .../GraphQL/Types/Contents/FieldVisitor.cs | 25 ++++---- .../Contents/GraphQL/Types/Contents/Names.cs | 60 ------------------- .../GraphQL/Types/Contents/SchemaInfo.cs | 50 ++++++++++++++++ .../Contents/GraphQL/Types/Extensions.cs | 15 +++++ .../Models/Fields/StringFieldPropertiesDto.cs | 5 ++ .../Models/Fields/TagsFieldPropertiesDto.cs | 5 ++ .../Contents/GraphQL/GraphQLMutationTests.cs | 13 +++- .../Contents/GraphQL/TestContent.cs | 44 ++++++++++++++ .../Contents/GraphQL/TestSchemas.cs | 32 +++++----- .../fields/types/string-ui.component.html | 15 +++++ .../fields/types/tags-ui.component.html | 17 ++++++ .../src/app/shared/services/schemas.types.ts | 2 + .../src/app/shared/state/schemas.forms.ts | 2 + 23 files changed, 243 insertions(+), 116 deletions(-) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/Names.cs diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 1fef6b24b..5690c5ba0 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -787,6 +787,8 @@ "schemas.export.recreateFields": "Recreate fields", "schemas.export.synchronize": "Synchronize", "schemas.field.allowedValues": "Allowed Values", + "schemas.field.createEnum": "Generate GraphQL Enum.", + "schemas.field.createEnumHint": "Check this checkbox to create a GraphQL enumeration for this field.", "schemas.field.defaultValue": "Default Value", "schemas.field.defaultValues": "Default Values", "schemas.field.defaultValuesHint": "Set the default value per language and override the default value property, if defined. Only use it if really needed.", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index ac66d0cff..9776a7d51 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -787,6 +787,8 @@ "schemas.export.recreateFields": "Ricrea i campi", "schemas.export.synchronize": "Sincronizza", "schemas.field.allowedValues": "Valori consentiti", + "schemas.field.createEnum": "Generate GraphQL Enum.", + "schemas.field.createEnumHint": "Check this checkbox to create a GraphQL enumeration for this field.", "schemas.field.defaultValue": "Valore predefinito", "schemas.field.defaultValues": "Valori predefiniti", "schemas.field.defaultValuesHint": "Imposta il valore predefinito per la lingua e sovrascrivere il valore predefinito, se definito. Usalo solo se davvero necessario.", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index 53815c466..926fb3938 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -787,6 +787,8 @@ "schemas.export.recreateFields": "Velden opnieuw maken", "schemas.export.synchronize": "Synchroniseren", "schemas.field.allowedValues": "Toegestane waarden", + "schemas.field.createEnum": "Generate GraphQL Enum.", + "schemas.field.createEnumHint": "Check this checkbox to create a GraphQL enumeration for this field.", "schemas.field.defaultValue": "Standaardwaarde", "schemas.field.defaultValues": "Standaardwaardes", "schemas.field.defaultValuesHint": "Stel de standaardwaarde per taal in en overschrijf de standaardwaarde-eigenschap, indien gedefinieerd. Gebruik het alleen als het echt nodig is.", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json index e4bafdc99..5c2545317 100644 --- a/backend/i18n/frontend_zh.json +++ b/backend/i18n/frontend_zh.json @@ -787,6 +787,8 @@ "schemas.export.recreateFields": "重新创建字段", "schemas.export.synchronize": "同步", "schemas.field.allowedValues": "允许的值", + "schemas.field.createEnum": "Generate GraphQL Enum.", + "schemas.field.createEnumHint": "Check this checkbox to create a GraphQL enumeration for this field.", "schemas.field.defaultValue": "Default Value", "schemas.field.defaultValues": "默认值", "schemas.field.defaultValuesHint": "设置每种语言的默认值并覆盖默认值属性(如果已定义)。仅在真正需要时才使用它。", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 1fef6b24b..5690c5ba0 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -787,6 +787,8 @@ "schemas.export.recreateFields": "Recreate fields", "schemas.export.synchronize": "Synchronize", "schemas.field.allowedValues": "Allowed Values", + "schemas.field.createEnum": "Generate GraphQL Enum.", + "schemas.field.createEnumHint": "Check this checkbox to create a GraphQL enumeration for this field.", "schemas.field.defaultValue": "Default Value", "schemas.field.defaultValues": "Default Values", "schemas.field.defaultValuesHint": "Set the default value per language and override the default value property, if defined. Only use it if really needed.", diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs index 8930e40a4..a9daab01d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs @@ -39,6 +39,8 @@ namespace Squidex.Domain.Apps.Core.Schemas public bool InlineEditable { get; init; } + public bool CreateEnum { get; init; } + public StringContentType ContentType { get; init; } public StringFieldEditor Editor { get; init; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs index 98c8733da..5da7af4cb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs @@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas public int? MaxItems { get; init; } + public bool CreateEnum { get; init; } + public TagsFieldEditor Editor { get; init; } public TagsFieldNormalization Normalization { get; init; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs index c98b28bcb..a2a996423 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types private readonly Dictionary componentTypes = new Dictionary(ReferenceEqualityComparer.Instance); private readonly Dictionary contentTypes = new Dictionary(ReferenceEqualityComparer.Instance); private readonly Dictionary contentResultTypes = new Dictionary(ReferenceEqualityComparer.Instance); - private readonly Dictionary enumTypes = new Dictionary(); + private readonly Dictionary enumTypes = new Dictionary(); private readonly FieldVisitor fieldVisitor; private readonly FieldInputVisitor fieldInputVisitor; private readonly PartitionResolver partitionResolver; @@ -151,9 +151,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return componentTypes.GetOrDefault(schema); } - public EnumerationGraphType GetEnumeration(string name, IEnumerable values) + public EnumerationGraphType? GetEnumeration(string name, IEnumerable values) { - return enumTypes.GetOrAdd(name, x => new FieldEnumType(name, values)); + return enumTypes.GetOrAdd(name, x => FieldEnumType.TryCreate(name, values)); } public IEnumerable> GetAllContentTypes() diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs index 674668366..f52d4f087 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldEnumType.cs @@ -6,29 +6,30 @@ // ========================================================================== using GraphQL.Types; -using Squidex.Text; +using GraphQL.Utilities; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents { - public sealed class FieldEnumType : EnumerationGraphType + public sealed class FieldEnumType : EnumerationGraphType { - public FieldEnumType(string name, IEnumerable values) + public FieldEnumType(string name, IEnumerable values) { Name = name; - // Avoid conflicts with duplicate names. - var names = new Names(); - foreach (var value in values) { - if (!Equals(value, null)) - { - // Get rid of special characters. - var valueName = value.ToString()!.Slugify().ToPascalCase(); + AddValue(value, null, value); + } + } - AddValue(names[valueName], null, value); - } + public static FieldEnumType? TryCreate(string name, IEnumerable values) + { + if (!values.All(x => x.IsValidName(NamedElement.EnumValue)) || !values.Any()) + { + return null; } + + return new FieldEnumType(name, values); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldInputVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldInputVisitor.cs index 9031739a2..7d545c5c5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldInputVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldInputVisitor.cs @@ -76,34 +76,41 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents public IGraphType? Visit(IField field, FieldInfo args) { - if (field.Properties?.AllowedValues?.Count > 0) - { - return builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); - } - return AllTypes.Float; } public IGraphType? Visit(IField field, FieldInfo args) { - if (field.Properties?.AllowedValues?.Count > 0) + var type = AllTypes.String; + + if (field.Properties?.AllowedValues?.Count > 0 && field.Properties.CreateEnum) { - return builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); + var @enum = builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); + + if (@enum != null) + { + type = @enum; + } } - return AllTypes.String; + return type; } public IGraphType? Visit(IField field, FieldInfo args) { - if (field.Properties?.AllowedValues?.Count > 0) + var type = AllTypes.Strings; + + if (field.Properties?.AllowedValues?.Count > 0 && field.Properties.CreateEnum) { var @enum = builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); - return new ListGraphType(new NonNullGraphType(@enum)); + if (@enum != null) + { + type = new ListGraphType(new NonNullGraphType(@enum)); + } } - return AllTypes.Strings; + return type; } public IGraphType? Visit(IField field, FieldInfo args) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs index 272143811..b1e999b06 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs @@ -164,23 +164,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) { - var type = AllTypes.Float; - - if (field.Properties?.AllowedValues?.Count > 0) - { - type = builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); - } - - return (type, JsonNumber, null); + return (AllTypes.Float, JsonNumber, null); } public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) { var type = AllTypes.String; - if (field.Properties?.AllowedValues?.Count > 0) + if (field.Properties?.AllowedValues?.Count > 0 && field.Properties.CreateEnum) { - type = builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); + var @enum = builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); + + if (@enum != null) + { + type = @enum; + } } return (type, JsonString, null); @@ -190,11 +188,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents { var type = AllTypes.Strings; - if (field.Properties?.AllowedValues?.Count > 0) + if (field.Properties?.AllowedValues?.Count > 0 && field.Properties.CreateEnum) { var @enum = builder.GetEnumeration(args.EnumName, field.Properties.AllowedValues); - type = new ListGraphType(new NonNullGraphType(@enum)); + if (@enum != null) + { + type = new ListGraphType(new NonNullGraphType(@enum)); + } } return (type, JsonStrings, null); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/Names.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/Names.cs deleted file mode 100644 index 04b6da64e..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/Names.cs +++ /dev/null @@ -1,60 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents -{ - internal sealed class Names - { - // Reserver names that are used for other GraphQL types. - private static readonly HashSet ReservedNames = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Asset", - "AssetResultDto", - "Content", - "Component", - "EntityCreatedResultDto", - "EntitySavedResultDto", - "JsonScalar", - "JsonPrimitive", - "User" - }; - private readonly Dictionary takenNames = new Dictionary(); - - public string this[string name, bool isEntity = true] - { - get => GetName(name, isEntity); - } - - private string GetName(string name, bool isEntity) - { - Guard.NotNullOrEmpty(name); - - if (!char.IsLetter(name[0])) - { - name = "gql_" + name; - } - else if (isEntity && ReservedNames.Contains(name)) - { - name = $"{name}Entity"; - } - - // Avoid duplicate names. - if (!takenNames.TryGetValue(name, out var offset)) - { - takenNames[name] = 0; - return name; - } - - takenNames[name] = ++offset; - - // Add + 1 to all offsets for backwards-compatibility. - return $"{name}{offset + 1}"; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs index f64b54f4e..366a03571 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs @@ -7,6 +7,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; using Squidex.Text; #pragma warning disable MA0048 // File name must match type name @@ -154,4 +155,53 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents return new FieldInfo(rootField, typeName, names, parentNames, fieldInfos); } } + + internal sealed class Names + { + // Reserver names that are used for other GraphQL types. + private static readonly HashSet ReservedNames = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Asset", + "AssetResultDto", + "Content", + "Component", + "EntityCreatedResultDto", + "EntitySavedResultDto", + "JsonScalar", + "JsonPrimitive", + "User" + }; + private readonly Dictionary takenNames = new Dictionary(); + + public string this[string name, bool isEntity = true] + { + get => GetName(name, isEntity); + } + + private string GetName(string name, bool isEntity) + { + Guard.NotNullOrEmpty(name); + + if (!char.IsLetter(name[0])) + { + name = "gql_" + name; + } + else if (isEntity && ReservedNames.Contains(name)) + { + name = $"{name}Entity"; + } + + // Avoid duplicate names. + if (!takenNames.TryGetValue(name, out var offset)) + { + takenNames[name] = 0; + return name; + } + + takenNames[name] = ++offset; + + // Add + 1 to all offsets for backwards-compatibility. + return $"{name}{offset + 1}"; + } + } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs index 83f56ac20..15843fadc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs @@ -7,6 +7,7 @@ using GraphQL; using GraphQL.Types; +using GraphQL.Utilities; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -52,6 +53,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types } } + public static bool IsValidName(this string? name, NamedElement type) + { + try + { + NameValidator.ValidateDefault(name!, type); + + return true; + } + catch + { + return false; + } + } + internal static FieldType WithSourceName(this FieldType field, string value) { return field.WithMetadata(nameof(SourceName), value); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs index 8f11b8101..faabf3529 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs @@ -83,6 +83,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public bool InlineEditable { get; set; } + /// + /// Indicates whether GraphQL Enum should be created. + /// + public bool CreateEnum { get; init; } + /// /// How the string content should be interpreted. /// diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs index 8c78eef92..4a270fcff 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs @@ -38,6 +38,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public ReadonlyList? AllowedValues { get; set; } + /// + /// Indicates whether GraphQL Enum should be created. + /// + public bool CreateEnum { get; init; } + /// /// The editor that is used to manage this field. /// diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index d926c30a1..120638005 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -739,9 +739,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id); var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented); - var dataString = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":").Replace(".0", string.Empty, StringComparison.Ordinal); - query = query.Replace("", dataString, StringComparison.Ordinal); + // Use Properties without quotes. + dataJson = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":"); + + // Use pure integer numbers. + dataJson = dataJson.Replace(".0", string.Empty, StringComparison.Ordinal); + + // Use enum values whithout quotes. + dataJson = dataJson.Replace("\"EnumA\"", "EnumA", StringComparison.Ordinal); + dataJson = dataJson.Replace("\"EnumB\"", "EnumB", StringComparison.Ordinal); + + query = query.Replace("", dataJson, StringComparison.Ordinal); } return query; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs index f14b7f02e..95542ba85 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs @@ -45,6 +45,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL myString { iv } + myStringEnum { + iv + } myLocalizedString { de_DE } @@ -88,6 +91,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL myTags { iv } + myTagsEnum { + iv + } myArray { iv { nestedNumber @@ -123,6 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL myJson myJsonValue: myJson(path: ""value"") myString + myStringEnum myLocalizedString myNumber myBoolean @@ -146,6 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } } myTags + myTagsEnum myArray { nestedNumber nestedBoolean @@ -164,6 +172,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL .AddField("my-string", new ContentFieldData() .AddInvariant(null)) + .AddField("my-string-enum", + new ContentFieldData() + .AddInvariant("EnumA")) .AddField("my-assets", new ContentFieldData() .AddInvariant(JsonValue.Array(assetId.ToString()))) @@ -179,6 +190,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL .AddField("my-tags", new ContentFieldData() .AddInvariant(JsonValue.Array("tag1", "tag2"))) + .AddField("my-tags-enum", + new ContentFieldData() + .AddInvariant(JsonValue.Array("EnumA", "EnumB"))) .AddField("my-references", new ContentFieldData() .AddInvariant(JsonValue.Array(refId.ToString()))) @@ -353,6 +367,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { iv = (string?)null, }, + ["myStringEnum"] = new + { + iv = "EnumA", + }, ["myLocalizedString"] = new { de_DE = "de-DE" @@ -409,6 +427,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL "tag2" } }, + ["myTagsEnum"] = new + { + iv = new[] + { + "EnumA", + "EnumB" + } + }, ["myArray"] = new { iv = new[] @@ -476,6 +502,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { iv = (string?)null, }, + ["myStringEnum"] = new + { + iv = "EnumA", + }, ["myLocalizedString"] = new { de_DE = "de-DE" @@ -558,6 +588,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL "tag2" } }, + ["myTagsEnum"] = new + { + iv = new[] + { + "EnumA", + "EnumB" + } + }, ["myArray"] = new { iv = new[] @@ -589,6 +627,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }, ["myJsonValue"] = 1, ["myString"] = null, + ["myStringEnum"] = "EnumA", ["myLocalizedString"] = "de-DE", ["myNumber"] = 1.0, ["myBoolean"] = true, @@ -641,6 +680,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL "tag1", "tag2" }, + ["myTagsEnum"] = new[] + { + "EnumA", + "EnumB" + }, ["myArray"] = new[] { new diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs index d4c95db47..3a95df24f 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs @@ -36,6 +36,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL .Publish() .AddString(1, "schemaRef2Field", Partitioning.Invariant)); + var enums = ReadonlyList.Create("EnumA", "EnumB", "EnumC"); + Default = Mocks.Schema(TestApp.DefaultId, DefaultId, new Schema(DefaultId.Name) .Publish() @@ -43,34 +45,32 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL new JsonFieldProperties()) .AddString(2, "my-string", Partitioning.Invariant, new StringFieldProperties()) - .AddString(3, "my-localized-string", Partitioning.Language, + .AddString(3, "my-string-enum", Partitioning.Invariant, + new StringFieldProperties { AllowedValues = enums, CreateEnum = true }) + .AddString(4, "my-localized-string", Partitioning.Language, new StringFieldProperties()) - .AddString(4, "my-string-enum", Partitioning.Invariant, - new StringFieldProperties { AllowedValues = ReadonlyList.Create("A", "B", "C") }) .AddNumber(5, "my-number", Partitioning.Invariant, new NumberFieldProperties()) - .AddNumber(6, "my-number-enum", Partitioning.Invariant, - new NumberFieldProperties { AllowedValues = ReadonlyList.Create(1.0, 2.0, 3.0) }) - .AddAssets(7, "my-assets", Partitioning.Invariant, + .AddAssets(6, "my-assets", Partitioning.Invariant, new AssetsFieldProperties()) - .AddBoolean(8, "my-boolean", Partitioning.Invariant, + .AddBoolean(7, "my-boolean", Partitioning.Invariant, new BooleanFieldProperties()) - .AddDateTime(9, "my-datetime", Partitioning.Invariant, + .AddDateTime(8, "my-datetime", Partitioning.Invariant, new DateTimeFieldProperties()) - .AddReferences(10, "my-references", Partitioning.Invariant, + .AddReferences(9, "my-references", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = Ref1Id.Id }) - .AddReferences(11, "my-union", Partitioning.Invariant, + .AddReferences(10, "my-union", Partitioning.Invariant, new ReferencesFieldProperties()) - .AddGeolocation(12, "my-geolocation", Partitioning.Invariant, + .AddGeolocation(11, "my-geolocation", Partitioning.Invariant, new GeolocationFieldProperties()) - .AddComponent(13, "my-component", Partitioning.Invariant, + .AddComponent(12, "my-component", Partitioning.Invariant, new ComponentFieldProperties { SchemaId = Ref1Id.Id }) - .AddComponents(14, "my-components", Partitioning.Invariant, + .AddComponents(13, "my-components", Partitioning.Invariant, new ComponentsFieldProperties { SchemaIds = ReadonlyList.Create(Ref1.Id, Ref2.Id) }) - .AddTags(15, "my-tags", Partitioning.Invariant, + .AddTags(14, "my-tags", Partitioning.Invariant, new TagsFieldProperties()) - .AddTags(16, "my-tags-enum", Partitioning.Invariant, - new TagsFieldProperties { AllowedValues = ReadonlyList.Create("A", "B", "C") }) + .AddTags(15, "my-tags-enum", Partitioning.Invariant, + new TagsFieldProperties { AllowedValues = enums, CreateEnum = true }) .AddArray(100, "my-array", Partitioning.Invariant, f => f .AddBoolean(121, "nested-boolean", new BooleanFieldProperties()) diff --git a/frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.html b/frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.html index 17cac7785..26fd42258 100644 --- a/frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.html +++ b/frontend/src/app/features/schemas/pages/schema/fields/types/string-ui.component.html @@ -33,6 +33,21 @@ +
+
+
+ + +
+ + + {{ 'schemas.field.createEnumHint' | sqxTranslate }} + +
+
+
diff --git a/frontend/src/app/features/schemas/pages/schema/fields/types/tags-ui.component.html b/frontend/src/app/features/schemas/pages/schema/fields/types/tags-ui.component.html index 1cbdf43f7..e860e4a5c 100644 --- a/frontend/src/app/features/schemas/pages/schema/fields/types/tags-ui.component.html +++ b/frontend/src/app/features/schemas/pages/schema/fields/types/tags-ui.component.html @@ -10,6 +10,7 @@
+
@@ -23,6 +24,7 @@
+
@@ -30,4 +32,19 @@
+ +
+
+
+ + +
+ + + {{ 'schemas.field.createEnumHint' | sqxTranslate }} + +
+
\ No newline at end of file diff --git a/frontend/src/app/shared/services/schemas.types.ts b/frontend/src/app/shared/services/schemas.types.ts index 1cf2cb368..88a916870 100644 --- a/frontend/src/app/shared/services/schemas.types.ts +++ b/frontend/src/app/shared/services/schemas.types.ts @@ -429,6 +429,7 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto { public readonly fieldType = 'String'; public readonly allowedValues?: ReadonlyArray; + public readonly createEnum: boolean = false; public readonly defaultValue?: string; public readonly defaultValues?: DefaultValue; public readonly editor: StringFieldEditor = 'Input'; @@ -466,6 +467,7 @@ export class TagsFieldPropertiesDto extends FieldPropertiesDto { public readonly fieldType = 'Tags'; public readonly allowedValues?: ReadonlyArray; + public readonly createEnum: boolean = false; public readonly defaultValue?: ReadonlyArray; public readonly defaultValues?: DefaultValue>; public readonly editor: TagsFieldEditor = 'Tags'; diff --git a/frontend/src/app/shared/state/schemas.forms.ts b/frontend/src/app/shared/state/schemas.forms.ts index f834f389c..f8e7403ba 100644 --- a/frontend/src/app/shared/state/schemas.forms.ts +++ b/frontend/src/app/shared/state/schemas.forms.ts @@ -326,6 +326,7 @@ export class EditFieldFormVisitor implements FieldPropertiesVisitor { public visitString() { this.config['allowedValues'] = new FormControl(undefined); this.config['contentType'] = new FormControl(undefined); + this.config['createEnum'] = new FormControl(undefined); this.config['defaultValue'] = new FormControl(undefined); this.config['defaultValues'] = new FormControl(undefined); this.config['folderId'] = new FormControl(undefined); @@ -343,6 +344,7 @@ export class EditFieldFormVisitor implements FieldPropertiesVisitor { public visitTags() { this.config['allowedValues'] = new FormControl(undefined); + this.config['createEnum'] = new FormControl(undefined); this.config['defaultValue'] = new FormControl(undefined); this.config['defaultValues'] = new FormControl(undefined); this.config['maxItems'] = new FormControl(undefined);