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. 58
      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, return await context.QueryContentsAsync(fieldContext.FieldDefinition.SchemaId(), q,
fieldContext.CancellationToken); 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 public static class Create

58
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> internal sealed class ContentGraphType : ObjectGraphType<IEnrichedContentEntity>
{ {
private readonly DomainId schemaId;
public ContentGraphType(SchemaInfo schemaInfo) public ContentGraphType(SchemaInfo schemaInfo)
{ {
// The name is used for equal comparison. Therefore it is important to treat it as readonly. // The name is used for equal comparison. Therefore it is important to treat it as readonly.
Name = schemaInfo.ContentType; Name = schemaInfo.ContentType;
IsTypeOf = CheckType;
schemaId = schemaInfo.Schema.Id;
}
private bool CheckType(object value)
{
return value is IContentEntity content && content.SchemaId?.Id == schemaId;
} }
public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas) public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)
{ {
var schemaId = schemaInfo.Schema.Id;
IsTypeOf = value =>
{
return value is IContentEntity content && content.SchemaId?.Id == schemaId;
};
AddField(ContentFields.Id); AddField(ContentFields.Id);
AddField(ContentFields.Version); AddField(ContentFields.Version);
AddField(ContentFields.Created); 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); AddReferencingQueries(builder, other);
} }
foreach (var other in allSchemas.Where(x => IsReference(schemaInfo, x)))
{
AddReferencesQueries(builder, other);
}
AddResolvedInterface(builder.SharedTypes.ContentInterface); AddResolvedInterface(builder.SharedTypes.ContentInterface);
Description = $"The structure of a {schemaInfo.DisplayName} content type."; Description = $"The structure of a {schemaInfo.DisplayName} content type.";
@ -109,12 +110,37 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}).WithSchemaId(referencingSchemaInfo); }).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) switch (field)
{ {
@ -124,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
reference.Properties.SchemaIds.Count == 0 || reference.Properties.SchemaIds.Count == 0 ||
reference.Properties.SchemaIds.Contains(schemaId); reference.Properties.SchemaIds.Contains(schemaId);
case IArrayField arrayField: case IArrayField arrayField:
return arrayField.Fields.Any(IsReferencingThis); return arrayField.Fields.Any(x => IsReferencing(x, schemaId));
} }
return false; 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); }", 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)); .Returns(ResultList.CreateFrom<IEnrichedAssetEntity>(1));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -158,7 +159,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", assetId); }", 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)); .Returns(ResultList.CreateFrom(1, asset));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -292,7 +294,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", 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)); .Returns(ResultList.CreateFrom<IEnrichedContentEntity>(1));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -321,7 +324,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", 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)); .Returns(ResultList.CreateFrom(10, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -350,7 +354,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", 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)); .Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -423,10 +428,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", 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)); .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)); .Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -489,7 +496,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentRefId); }", 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)); .Returns(ResultList.CreateFrom(1, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(),
@ -553,12 +561,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentRefId); }", 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)); .Returns(ResultList.CreateFrom(1, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), content.SchemaId.Id.ToString(), 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>._)) 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 }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -571,7 +580,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentRefId, id = contentRefId,
referencingMySchemaContentsWithTotal = new referencingMySchemaContentsWithTotal = new
{ {
total = 1, total = 10,
items = new[] items = new[]
{ {
new new
@ -594,6 +603,113 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
AssertResult(expected, result); 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] [Fact]
public async Task Should_also_fetch_union_contents_if_field_is_included_in_query() 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); }", 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)); .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)); .Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -693,10 +811,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", 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)); .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)); .Returns(ResultList.CreateFrom(0, assetRef));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -752,7 +872,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
} }
}", contentId); }", 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)); .Returns(ResultList.CreateFrom(1, content));
var result = await ExecuteAsync(new ExecutionOptions { Query = query }); var result = await ExecuteAsync(new ExecutionOptions { Query = query });

Loading…
Cancel
Save