Browse Source

Query references with filters and total and so on. (#850)

pull/851/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
70f2c78700
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs
  2. 56
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs
  3. 153
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

20
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs

@ -172,6 +172,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return await context.QueryContentsAsync(fieldContext.FieldDefinition.SchemaId(), q,
fieldContext.CancellationToken);
});
public static readonly IFieldResolver References = Resolvers.Async<IContentEntity, object?>(async (source, fieldContext, context) =>
{
var query = fieldContext.BuildODataQuery();
var q = Q.Empty.WithODataQuery(query).WithReferencing(source.Id).WithoutTotal();
return await context.QueryContentsAsync(fieldContext.FieldDefinition.SchemaId(), q,
fieldContext.CancellationToken);
});
public static readonly IFieldResolver ReferencesWithTotal = Resolvers.Async<IContentEntity, object?>(async (source, fieldContext, context) =>
{
var query = fieldContext.BuildODataQuery();
var q = Q.Empty.WithODataQuery(query).WithReferencing(source.Id);
return await context.QueryContentsAsync(fieldContext.FieldDefinition.SchemaId(), q,
fieldContext.CancellationToken);
});
}
public static class Create

56
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs

@ -14,25 +14,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ContentGraphType : ObjectGraphType<IEnrichedContentEntity>
{
private readonly DomainId schemaId;
public ContentGraphType(SchemaInfo schemaInfo)
{
// The name is used for equal comparison. Therefore it is important to treat it as readonly.
Name = schemaInfo.ContentType;
IsTypeOf = CheckType;
schemaId = schemaInfo.Schema.Id;
}
private bool CheckType(object value)
public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)
{
return value is IContentEntity content && content.SchemaId?.Id == schemaId;
}
var schemaId = schemaInfo.Schema.Id;
public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)
IsTypeOf = value =>
{
return value is IContentEntity content && content.SchemaId?.Id == schemaId;
};
AddField(ContentFields.Id);
AddField(ContentFields.Version);
AddField(ContentFields.Created);
@ -74,11 +70,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
});
}
foreach (var other in allSchemas.Where(IsReferencingThis))
foreach (var other in allSchemas.Where(x => IsReference(x, schemaInfo)))
{
AddReferencingQueries(builder, other);
}
foreach (var other in allSchemas.Where(x => IsReference(schemaInfo, x)))
{
AddReferencesQueries(builder, other);
}
AddResolvedInterface(builder.SharedTypes.ContentInterface);
Description = $"The structure of a {schemaInfo.DisplayName} content type.";
@ -109,12 +110,37 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}).WithSchemaId(referencingSchemaInfo);
}
private bool IsReferencingThis(SchemaInfo other)
private void AddReferencesQueries(Builder builder, SchemaInfo referencesSchemaInfo)
{
var contentType = builder.GetContentType(referencesSchemaInfo);
AddField(new FieldType
{
Name = $"references{referencesSchemaInfo.TypeName}Contents",
Arguments = ContentActions.QueryOrReferencing.Arguments,
ResolvedType = new ListGraphType(new NonNullGraphType(contentType)),
Resolver = ContentActions.QueryOrReferencing.References,
Description = $"Query {referencesSchemaInfo.DisplayName} content items."
}).WithSchemaId(referencesSchemaInfo);
var contentResultsTyp = builder.GetContentResultType(referencesSchemaInfo);
AddField(new FieldType
{
Name = $"references{referencesSchemaInfo.TypeName}ContentsWithTotal",
Arguments = ContentActions.QueryOrReferencing.Arguments,
ResolvedType = contentResultsTyp,
Resolver = ContentActions.QueryOrReferencing.ReferencesWithTotal,
Description = $"Query {referencesSchemaInfo.DisplayName} content items with total count."
}).WithSchemaId(referencesSchemaInfo);
}
private static bool IsReference(SchemaInfo from, SchemaInfo to)
{
return other.Schema.SchemaDef.Fields.Any(IsReferencingThis);
return from.Schema.SchemaDef.Fields.Any(x => IsReferencing(x, to.Schema.Id));
}
private bool IsReferencingThis(IField field)
private static bool IsReferencing(IField field, DomainId schemaId)
{
switch (field)
{
@ -124,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
reference.Properties.SchemaIds.Count == 0 ||
reference.Properties.SchemaIds.Contains(schemaId);
case IArrayField arrayField:
return arrayField.Fields.Any(IsReferencingThis);
return arrayField.Fields.Any(x => IsReferencing(x, schemaId));
}
return false;

153
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -129,7 +129,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", assetId);
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A<Q>.That.HasIdsWithoutTotal(assetId), A<CancellationToken>._))
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.HasIdsWithoutTotal(assetId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom<IEnrichedAssetEntity>(1));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -158,7 +159,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", assetId);
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A<Q>.That.HasIdsWithoutTotal(assetId), A<CancellationToken>._))
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.HasIdsWithoutTotal(assetId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, asset));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -292,7 +294,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom<IEnrichedContentEntity>(1));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -321,7 +324,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(10, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -350,7 +354,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -423,10 +428,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -489,7 +496,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentRefId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
@ -553,12 +561,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentRefId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Reference == contentRefId && !x.NoTotal), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
.Returns(ResultList.CreateFrom(10, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -571,7 +580,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentRefId,
referencingMySchemaContentsWithTotal = new
{
total = 1,
total = 10,
items = new[]
{
new
@ -594,6 +603,113 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
AssertResult(expected, result);
}
[Fact]
public async Task Should_also_fetch_references_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@"
query {
findMySchemaContent(id: '<ID>') {
id
referencesMyRefSchema1Contents(top: 30, skip: 5) {
id
}
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), contentRef.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Referencing == contentId && x.NoTotal), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, contentRef));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
var expected = new
{
data = new
{
findMySchemaContent = new
{
id = contentId,
referencesMyRefSchema1Contents = new[]
{
new
{
id = contentRefId
}
}
}
}
};
AssertResult(expected, result);
}
[Fact]
public async Task Should_also_fetch_references_contents_with_total_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@"
query {
findMySchemaContent(id: '<ID>') {
id
referencesMyRefSchema1ContentsWithTotal(top: 30, skip: 5) {
total
items {
id
}
}
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), contentRef.SchemaId.Id.ToString(),
A<Q>.That.Matches(x => x.QueryAsOdata == "?$top=30&$skip=5" && x.Referencing == contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(10, contentRef));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
var expected = new
{
data = new
{
findMySchemaContent = new
{
id = contentId,
referencesMyRefSchema1ContentsWithTotal = new
{
total = 10,
items = new[]
{
new
{
id = contentRefId
}
}
}
}
}
};
AssertResult(expected, result);
}
[Fact]
public async Task Should_also_fetch_union_contents_if_field_is_included_in_query()
{
@ -627,10 +743,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentRefId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -693,10 +811,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A<Q>.That.HasIdsWithoutTotal(assetRefId), A<CancellationToken>._))
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.HasIdsWithoutTotal(assetRefId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, assetRef));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -752,7 +872,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(),
A<Q>.That.HasIdsWithoutTotal(contentId), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query });

Loading…
Cancel
Save