Browse Source

Make components dynamic in graphql.

pull/710/head
Sebastian Stehle 5 years ago
parent
commit
59c4cfd439
  1. 30
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs
  2. 50
      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. 30
      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

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

@ -7,6 +7,7 @@
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
@ -18,19 +19,34 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var fieldInfo in schemaInfo.Fields)
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
if (fieldInfo.Field.RawProperties is ComponentFieldProperties ||
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
{
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
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);
if (resolver != null)
{
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
}
}
Description = $"The structure of the flat {schemaInfo.DisplayName} data type.";

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

@ -20,9 +20,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var fieldInfo in schemaInfo.Fields)
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
if (fieldInfo.Field.RawProperties is ComponentFieldProperties ||
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
{
var fieldGraphType = new ObjectGraphType
{
@ -36,22 +35,57 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
fieldGraphType.AddField(new FieldType
{
Name = partitionKey.EscapePartition(),
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Arguments = ContentActions.Json.Arguments,
ResolvedType = AllTypes.Json,
Resolver = FieldVisitor.JsonPath,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(partitionKey);
}
fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
fieldGraphType.Description = $"The dynamic structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Name = fieldInfo.FieldNameDynamic,
ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo);
}
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
{
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 = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(partitionKey);
}
fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
AddField(new FieldType
{
Name = fieldInfo.FieldName,
ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo);
}
}
}
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>
{
private static readonly IFieldResolver JsonNoop = CreateValueResolver((value, fieldContext, contex) => value);
private static readonly IFieldResolver JsonPath = CreateValueResolver(ContentActions.Json.Resolver);
public static readonly IFieldResolver JsonNoop = CreateValueResolver((value, fieldContext, contex) => value);
public static readonly IFieldResolver JsonPath = CreateValueResolver(ContentActions.Json.Resolver);
private static readonly IFieldResolver JsonBoolean = CreateValueResolver((value, fieldContext, contex) =>
{

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

@ -6,6 +6,7 @@
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
@ -18,19 +19,34 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var nestedFieldInfo in fieldInfo.Fields)
{
var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo);
if (resolvedType != null && resolver != null)
if (nestedFieldInfo.Field.RawProperties is ComponentFieldProperties ||
nestedFieldInfo.Field.RawProperties is ComponentsFieldProperties)
{
AddField(new FieldType
{
Name = nestedFieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
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);
if (resolvedType != null && resolver != null)
{
AddField(new FieldType
{
Name = nestedFieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo);
}
}
}
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)
{
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 FieldNameDynamic { get; }
public string DisplayName { get; }
public string LocalizedType { get; }
@ -107,12 +116,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
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();
Field = field;
Fields = fields;
FieldName = fieldName;
FieldNameDynamic = fieldNameDynamic;
LocalizedType = names[$"{typeName}Dto"];
LocalizedInputType = names[$"{typeName}InputDto"];
NestedInputType = names[$"{typeName}ChildInputDto"];
@ -125,23 +135,30 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
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;
if (rootField is IArrayField arrayField && arrayField.Fields.Count > 0)
{
var fieldNames = new Names();
var nestedNames = new Names();
fields = new List<FieldInfo>(arrayField.Fields.Count);
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.
private static readonly HashSet<string> ReservedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Content",
"Asset",
"AssetResultDto",
"Content",
"EntityCreatedResultDto",
"EntitySavedResultDto",
"JsonScalar",
"JsonPrimitive",
@ -192,7 +210,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
takenNames[name] = ++offset;
// Add + 1 to all offset for backwars compatibility.
// Add + 1 to all offsets for backwards-compatibility.
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.Infrastructure;
using Squidex.Infrastructure.ObjectPool;
using Squidex.Text;
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();
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);
}
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);
}
public static string SourceName(this FieldType field)
internal static string SourceName(this FieldType field)
{
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());
}
public static string SchemaId(this FieldType field)
internal static string SchemaId(this FieldType field)
{
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());
}
public static NamedId<DomainId> SchemaNamedId(this FieldType field)
internal static NamedId<DomainId> SchemaNamedId(this FieldType field)
{
return field.GetMetadata<NamedId<DomainId>>(nameof(SchemaNamedId));
}
@ -91,12 +106,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return field;
}
public static IGraphType Flatten(this QueryArgument type)
internal static IGraphType Flatten(this QueryArgument type)
{
return type.ResolvedType.Flatten();
}
public static IGraphType Flatten(this IGraphType type)
internal static IGraphType Flatten(this IGraphType type)
{
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>"))
{
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 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
{
data = TestContent.Data(content, schemaRefId1.Id, schemaRefId2.Id)
data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id)
};
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 NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
@ -69,10 +71,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
myGeolocation {
iv
}
myComponent {
myComponent__Dynamic {
iv
}
myComponents {
myComponents__Dynamic {
iv
}
myTags {
@ -246,11 +248,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
status = "DRAFT",
statusColor = "red",
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>
{
@ -305,7 +307,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
longitude = 20
}
},
["myComponent"] = new
["myComponent".AsDynamic(input)] = new
{
iv = new
{
@ -314,7 +316,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
value2 = 200
}
},
["myComponents"] = new
["myComponents".AsDynamic(input)] = new
{
iv = new[]
{

Loading…
Cancel
Save