Browse Source

Make components dynamic in graphql.

pull/710/head
Sebastian Stehle 5 years ago
parent
commit
59c4cfd439
  1. 16
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs
  2. 34
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs
  3. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs
  4. 16
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs
  5. 34
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs
  6. 37
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs
  7. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs
  8. 14
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

16
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs

@ -7,6 +7,7 @@
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
@ -17,6 +18,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Name = schemaInfo.DataFlatType; Name = schemaInfo.DataFlatType;
foreach (var fieldInfo in schemaInfo.Fields) foreach (var fieldInfo in schemaInfo.Fields)
{
if (fieldInfo.Field.RawProperties is ComponentFieldProperties ||
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
{
AddField(new FieldType
{
Name = fieldInfo.FieldNameDynamic,
Arguments = ContentActions.Json.Arguments,
ResolvedType = AllTypes.Json,
Resolver = FieldVisitor.JsonPath,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
else
{ {
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo); var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
@ -32,6 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}).WithSourceName(fieldInfo); }).WithSourceName(fieldInfo);
} }
} }
}
Description = $"The structure of the flat {schemaInfo.DisplayName} data type."; Description = $"The structure of the flat {schemaInfo.DisplayName} data type.";
} }

34
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs

@ -19,6 +19,39 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Name = schemaInfo.DataType; Name = schemaInfo.DataType;
foreach (var fieldInfo in schemaInfo.Fields) foreach (var fieldInfo in schemaInfo.Fields)
{
if (fieldInfo.Field.RawProperties is ComponentFieldProperties ||
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
{
var fieldGraphType = new ObjectGraphType
{
Name = fieldInfo.LocalizedType
};
var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
foreach (var partitionKey in partitioning.AllKeys)
{
fieldGraphType.AddField(new FieldType
{
Name = partitionKey.EscapePartition(),
Arguments = ContentActions.Json.Arguments,
ResolvedType = AllTypes.Json,
Resolver = FieldVisitor.JsonPath,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(partitionKey);
}
fieldGraphType.Description = $"The dynamic structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
AddField(new FieldType
{
Name = fieldInfo.FieldNameDynamic,
ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo);
}
else
{ {
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo); var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
@ -53,6 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}).WithSourceName(fieldInfo); }).WithSourceName(fieldInfo);
} }
} }
}
Description = $"The structure of the {schemaInfo.DisplayName} data type."; Description = $"The structure of the {schemaInfo.DisplayName} data type.";
} }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs

@ -20,8 +20,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
internal sealed class FieldVisitor : IFieldVisitor<(IGraphType?, IFieldResolver?, QueryArguments?), FieldInfo> internal sealed class FieldVisitor : IFieldVisitor<(IGraphType?, IFieldResolver?, QueryArguments?), FieldInfo>
{ {
private static readonly IFieldResolver JsonNoop = CreateValueResolver((value, fieldContext, contex) => value); public static readonly IFieldResolver JsonNoop = CreateValueResolver((value, fieldContext, contex) => value);
private static readonly IFieldResolver JsonPath = CreateValueResolver(ContentActions.Json.Resolver); public static readonly IFieldResolver JsonPath = CreateValueResolver(ContentActions.Json.Resolver);
private static readonly IFieldResolver JsonBoolean = CreateValueResolver((value, fieldContext, contex) => private static readonly IFieldResolver JsonBoolean = CreateValueResolver((value, fieldContext, contex) =>
{ {

16
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
@ -17,6 +18,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Name = fieldInfo.NestedType; Name = fieldInfo.NestedType;
foreach (var nestedFieldInfo in fieldInfo.Fields) foreach (var nestedFieldInfo in fieldInfo.Fields)
{
if (nestedFieldInfo.Field.RawProperties is ComponentFieldProperties ||
nestedFieldInfo.Field.RawProperties is ComponentsFieldProperties)
{
AddField(new FieldType
{
Name = nestedFieldInfo.FieldNameDynamic,
Arguments = ContentActions.Json.Arguments,
ResolvedType = AllTypes.Json,
Resolver = FieldVisitor.JsonPath,
Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo);
}
else
{ {
var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo); var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo);
@ -32,6 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}).WithSourceName(nestedFieldInfo); }).WithSourceName(nestedFieldInfo);
} }
} }
}
Description = $"The structure of the {fieldInfo.DisplayName} nested schema."; Description = $"The structure of the {fieldInfo.DisplayName} nested schema.";
} }

34
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs

@ -76,7 +76,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var field in schema.SchemaDef.Fields) foreach (var field in schema.SchemaDef.Fields)
{ {
fields.Add(FieldInfo.Build(field, fieldNames[field], names[$"{typeName}Data{field.TypeName()}"], names)); var fieldName = fieldNames[field];
fields.Add(FieldInfo.Build(
field,
fieldName,
fieldNames[fieldName.AsDynamic()],
names[$"{typeName}Data{field.TypeName()}"],
names));
} }
} }
@ -93,6 +100,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public string FieldName { get; } public string FieldName { get; }
public string FieldNameDynamic { get; }
public string DisplayName { get; } public string DisplayName { get; }
public string LocalizedType { get; } public string LocalizedType { get; }
@ -107,12 +116,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public IReadOnlyList<FieldInfo> Fields { get; } public IReadOnlyList<FieldInfo> Fields { get; }
private FieldInfo(IField field, string fieldName, string typeName, IReadOnlyList<FieldInfo> fields, Names names) private FieldInfo(IField field, string fieldName, string fieldNameDynamic, string typeName, IReadOnlyList<FieldInfo> fields, Names names)
{ {
DisplayName = field.DisplayName(); DisplayName = field.DisplayName();
Field = field; Field = field;
Fields = fields; Fields = fields;
FieldName = fieldName; FieldName = fieldName;
FieldNameDynamic = fieldNameDynamic;
LocalizedType = names[$"{typeName}Dto"]; LocalizedType = names[$"{typeName}Dto"];
LocalizedInputType = names[$"{typeName}InputDto"]; LocalizedInputType = names[$"{typeName}InputDto"];
NestedInputType = names[$"{typeName}ChildInputDto"]; NestedInputType = names[$"{typeName}ChildInputDto"];
@ -125,23 +135,30 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return FieldName; return FieldName;
} }
internal static FieldInfo Build(IRootField rootField, string fieldName, string typeName, Names names) internal static FieldInfo Build(IRootField rootField, string fieldName, string fieldNameDynamic, string typeName, Names names)
{ {
var fields = EmptyFields; var fields = EmptyFields;
if (rootField is IArrayField arrayField && arrayField.Fields.Count > 0) if (rootField is IArrayField arrayField && arrayField.Fields.Count > 0)
{ {
var fieldNames = new Names(); var nestedNames = new Names();
fields = new List<FieldInfo>(arrayField.Fields.Count); fields = new List<FieldInfo>(arrayField.Fields.Count);
foreach (var field in arrayField.Fields) foreach (var field in arrayField.Fields)
{ {
fields.Add(new FieldInfo(field, fieldNames[field], $"{typeName}{field.TypeName()}", EmptyFields, names)); var nestedName = nestedNames[field];
fields.Add(new FieldInfo(field,
nestedName,
nestedNames[nestedName.AsDynamic()],
$"{typeName}{field.TypeName()}",
EmptyFields,
names));
} }
} }
return new FieldInfo(rootField, fieldName, typeName, fields, names); return new FieldInfo(rootField, fieldName, fieldNameDynamic, typeName, fields, names);
} }
} }
@ -150,9 +167,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
// Reserver names that are used for other GraphQL types. // Reserver names that are used for other GraphQL types.
private static readonly HashSet<string> ReservedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> ReservedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"Content",
"Asset", "Asset",
"AssetResultDto", "AssetResultDto",
"Content",
"EntityCreatedResultDto",
"EntitySavedResultDto", "EntitySavedResultDto",
"JsonScalar", "JsonScalar",
"JsonPrimitive", "JsonPrimitive",
@ -192,7 +210,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
takenNames[name] = ++offset; takenNames[name] = ++offset;
// Add + 1 to all offset for backwars compatibility. // Add + 1 to all offsets for backwards-compatibility.
return $"{name}{offset + 1}"; return $"{name}{offset + 1}";
} }
} }

37
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs

@ -11,12 +11,27 @@ using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool; using Squidex.Infrastructure.ObjectPool;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
internal static class Extensions public static class Extensions
{ {
public static string BuildODataQuery(this IResolveFieldContext context) public static string AsDynamic(this string value, bool input = false)
{
#pragma warning disable RECS0015 // If an extension method is called as static method convert it to method syntax
var result = CasingExtensions.ToCamelCase(value);
#pragma warning restore RECS0015 // If an extension method is called as static method convert it to method syntax
if (!input)
{
result = $"{result}__Dynamic";
}
return result;
}
internal static string BuildODataQuery(this IResolveFieldContext context)
{ {
var sb = DefaultPools.StringBuilder.Get(); var sb = DefaultPools.StringBuilder.Get();
try try
@ -49,37 +64,37 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
} }
} }
public static FieldType WithSourceName(this FieldType field, string value) internal static FieldType WithSourceName(this FieldType field, string value)
{ {
return field.WithMetadata(nameof(SourceName), value); return field.WithMetadata(nameof(SourceName), value);
} }
public static FieldType WithSourceName(this FieldType field, FieldInfo value) internal static FieldType WithSourceName(this FieldType field, FieldInfo value)
{ {
return field.WithMetadata(nameof(SourceName), value.Field.Name); return field.WithMetadata(nameof(SourceName), value.Field.Name);
} }
public static string SourceName(this FieldType field) internal static string SourceName(this FieldType field)
{ {
return field.GetMetadata<string>(nameof(SourceName)); return field.GetMetadata<string>(nameof(SourceName));
} }
public static FieldType WithSchemaId(this FieldType field, SchemaInfo value) internal static FieldType WithSchemaId(this FieldType field, SchemaInfo value)
{ {
return field.WithMetadata(nameof(SchemaId), value.Schema.Id.ToString()); return field.WithMetadata(nameof(SchemaId), value.Schema.Id.ToString());
} }
public static string SchemaId(this FieldType field) internal static string SchemaId(this FieldType field)
{ {
return field.GetMetadata<string>(nameof(SchemaId)); return field.GetMetadata<string>(nameof(SchemaId));
} }
public static FieldType WithSchemaNamedId(this FieldType field, SchemaInfo value) internal static FieldType WithSchemaNamedId(this FieldType field, SchemaInfo value)
{ {
return field.WithMetadata(nameof(SchemaNamedId), value.Schema.NamedId()); return field.WithMetadata(nameof(SchemaNamedId), value.Schema.NamedId());
} }
public static NamedId<DomainId> SchemaNamedId(this FieldType field) internal static NamedId<DomainId> SchemaNamedId(this FieldType field)
{ {
return field.GetMetadata<NamedId<DomainId>>(nameof(SchemaNamedId)); return field.GetMetadata<NamedId<DomainId>>(nameof(SchemaNamedId));
} }
@ -91,12 +106,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return field; return field;
} }
public static IGraphType Flatten(this QueryArgument type) internal static IGraphType Flatten(this QueryArgument type)
{ {
return type.ResolvedType.Flatten(); return type.ResolvedType.Flatten();
} }
public static IGraphType Flatten(this IGraphType type) internal static IGraphType Flatten(this IGraphType type)
{ {
if (type is IProvideResolvedType provider) if (type is IProvideResolvedType provider)
{ {

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs

@ -736,7 +736,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
if (query.Contains("<DATA>")) if (query.Contains("<DATA>"))
{ {
var data = TestContent.Data(content, schemaRefId1.Id, schemaRefId2.Id); var data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id);
var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented); var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented);
var dataString = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":").Replace(".0", string.Empty); var dataString = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":").Replace(".0", string.Empty);
@ -751,7 +751,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
var input = new var input = new
{ {
data = TestContent.Data(content, schemaRefId1.Id, schemaRefId2.Id) data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id)
}; };
return JObject.FromObject(input).ToInputs(); return JObject.FromObject(input).ToInputs();

14
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

@ -8,8 +8,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
@ -69,10 +71,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
myGeolocation { myGeolocation {
iv iv
} }
myComponent { myComponent__Dynamic {
iv iv
} }
myComponents { myComponents__Dynamic {
iv iv
} }
myTags { myTags {
@ -246,11 +248,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
status = "DRAFT", status = "DRAFT",
statusColor = "red", statusColor = "red",
url = $"contents/my-schema/{content.Id}", url = $"contents/my-schema/{content.Id}",
data = Data(content) data = Data(content, false)
}; };
} }
public static object Data(IContentEntity content, DomainId refId = default, DomainId assetId = default) public static object Data(IContentEntity content, bool input, DomainId refId = default, DomainId assetId = default)
{ {
var result = new Dictionary<string, object> var result = new Dictionary<string, object>
{ {
@ -305,7 +307,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
longitude = 20 longitude = 20
} }
}, },
["myComponent"] = new ["myComponent".AsDynamic(input)] = new
{ {
iv = new iv = new
{ {
@ -314,7 +316,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
value2 = 200 value2 = 200
} }
}, },
["myComponents"] = new ["myComponents".AsDynamic(input)] = new
{ {
iv = new[] iv = new[]
{ {

Loading…
Cancel
Save