Skip to content

Commit 1a220cf

Browse files
authored
Merge branch 'main' into gvkries/gql-17261
2 parents 7e25090 + b72ef2a commit 1a220cf

File tree

9 files changed

+174
-8
lines changed

9 files changed

+174
-8
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using GraphQL.Types;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Localization;
4+
using OrchardCore.ContentManagement;
5+
using OrchardCore.ContentManagement.GraphQL.Queries.Types;
6+
using OrchardCore.Lists.Models;
7+
8+
namespace OrchardCore.Lists.GraphQL;
9+
10+
internal sealed class ContainedPartContentItemTypeInitializer : IContentItemTypeInitializer
11+
{
12+
internal readonly IStringLocalizer S;
13+
14+
public ContainedPartContentItemTypeInitializer(IStringLocalizer<ContainedPartContentItemTypeInitializer> stringLocalizer)
15+
{
16+
S = stringLocalizer;
17+
}
18+
19+
public void Initialize(ContentItemType contentItemType, ISchema schema)
20+
{
21+
foreach (var type in schema.AdditionalTypeInstances)
22+
{
23+
// Get all types with a list part that can contain the current type.
24+
if (!type.Metadata.TryGetValue(nameof(ListPartSettings.ContainedContentTypes), out var containedTypes))
25+
{
26+
continue;
27+
}
28+
29+
if ((containedTypes as IEnumerable<string>)?.Any(ct => ct == contentItemType.Name) != true)
30+
{
31+
continue;
32+
}
33+
34+
var fieldType = schema.AdditionalTypeInstances.FirstOrDefault(t => t is ContainedQueryObjectType);
35+
36+
if (fieldType == null)
37+
{
38+
fieldType = ((IServiceProvider)schema).GetRequiredService<ContainedQueryObjectType>();
39+
schema.RegisterType(fieldType);
40+
}
41+
42+
contentItemType.Field<ContainedQueryObjectType>(type.Name.ToFieldName())
43+
.Description(S["The parent content item of type {0}.", type.Name])
44+
.Type(fieldType)
45+
.Resolve(context =>
46+
{
47+
return context.Source.ContentItem.As<ContainedPart>();
48+
});
49+
}
50+
}
51+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using GraphQL.Types;
2+
using OrchardCore.ContentManagement.GraphQL.Queries.Types;
3+
using OrchardCore.ContentManagement.Metadata.Models;
4+
using OrchardCore.Lists.Models;
5+
6+
namespace OrchardCore.Lists.GraphQL;
7+
8+
internal sealed class ContainedPartContentTypeBuilder : IContentTypeBuilder
9+
{
10+
public void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType)
11+
{
12+
foreach (var listPart in contentTypeDefinition.Parts.Where(p => p.PartDefinition.Name.Equals(nameof(ListPart), StringComparison.OrdinalIgnoreCase)))
13+
{
14+
var settings = listPart?.GetSettings<ListPartSettings>();
15+
if (settings == null)
16+
{
17+
continue;
18+
}
19+
20+
if (contentItemType.Metadata.TryGetValue(nameof(ListPartSettings.ContainedContentTypes), out var containedContentTypes) &&
21+
containedContentTypes is IEnumerable<string> existingContainedContentTypes)
22+
{
23+
contentItemType.Metadata[nameof(ListPartSettings.ContainedContentTypes)] = existingContainedContentTypes.Concat(settings.ContainedContentTypes).Distinct().ToArray();
24+
}
25+
else
26+
{
27+
contentItemType.Metadata[nameof(ListPartSettings.ContainedContentTypes)] = settings.ContainedContentTypes;
28+
}
29+
}
30+
}
31+
32+
public void Clear()
33+
{
34+
}
35+
}
Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using GraphQL.Types;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Microsoft.Extensions.Localization;
4+
using OrchardCore.Apis.GraphQL;
5+
using OrchardCore.ContentManagement;
6+
using OrchardCore.ContentManagement.GraphQL.Queries.Types;
37
using OrchardCore.Lists.Models;
48

59
namespace OrchardCore.Lists.GraphQL;
@@ -8,10 +12,25 @@ public class ContainedQueryObjectType : ObjectGraphType<ContainedPart>
812
{
913
public ContainedQueryObjectType(IStringLocalizer<ContainedQueryObjectType> S)
1014
{
11-
Name = "ContainedPart";
12-
Description = S["Represents a link to the parent content item, and the order that content item is represented."];
15+
Name = nameof(ContainedPart);
16+
Description = S["Represents a link to the parent content item and the order in which the current content item is represented."];
1317

1418
Field(x => x.ListContentItemId);
15-
Field(x => x.Order);
19+
20+
Field<ContentItemInterface, ContentItem>("listContentItem")
21+
.Description(S["the parent list content item"])
22+
.ResolveLockedAsync(async x =>
23+
{
24+
var contentItemId = x.Source.ListContentItemId;
25+
var contentManager = x.RequestServices.GetService<IContentManager>();
26+
27+
return await contentManager.GetAsync(contentItemId);
28+
});
29+
30+
Field(x => x.ListContentType)
31+
.Description(S["the content type of the list owning the current content item."]);
32+
33+
Field(x => x.Order)
34+
.Description(S["the order of the current content item in the list."]);
1635
}
1736
}

src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/Startup.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OrchardCore.Apis;
33
using OrchardCore.ContentManagement.GraphQL;
44
using OrchardCore.ContentManagement.GraphQL.Queries;
5+
using OrchardCore.ContentManagement.GraphQL.Queries.Types;
56
using OrchardCore.Lists.Indexes;
67
using OrchardCore.Lists.Models;
78
using OrchardCore.Modules;
@@ -18,5 +19,8 @@ public override void ConfigureServices(IServiceCollection services)
1819
services.AddObjectGraphType<ListPart, ListQueryObjectType>();
1920
services.AddTransient<IIndexAliasProvider, ContainedPartIndexAliasProvider>();
2021
services.AddWhereInputIndexPropertyProvider<ContainedPartIndex>();
22+
23+
services.AddScoped<IContentTypeBuilder, ContainedPartContentTypeBuilder>();
24+
services.AddTransient<IContentItemTypeInitializer, ContainedPartContentItemTypeInitializer>();
2125
}
2226
}

src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentTypeQuery.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,9 @@ public async Task BuildAsync(ISchema schema)
7979
{
8080
schema.Query.AddField(query);
8181
}
82-
else
83-
{
84-
// Register the content item type explicitly since it won't be discovered from the root 'query' type.
85-
schema.RegisterType(typeType);
86-
}
82+
83+
// Register the content item type explicitly to make it easier to find it.
84+
schema.RegisterType(typeType);
8785

8886
if (!string.IsNullOrEmpty(stereotype))
8987
{

src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,19 @@ private static async ValueTask<string> RenderShapeAsync(IResolveFieldContext<Con
100100

101101
return sw.ToString();
102102
}
103+
104+
public override void Initialize(ISchema schema)
105+
{
106+
if (schema is IServiceProvider serviceProvider)
107+
{
108+
var initializers = serviceProvider.GetServices<IContentItemTypeInitializer>();
109+
110+
foreach (var initializer in initializers)
111+
{
112+
initializer.Initialize(this, schema);
113+
}
114+
}
115+
116+
base.Initialize(schema);
117+
}
103118
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using GraphQL.Types;
2+
3+
namespace OrchardCore.ContentManagement.GraphQL.Queries.Types;
4+
5+
public interface IContentItemTypeInitializer
6+
{
7+
void Initialize(ContentItemType contentItemType, ISchema schema);
8+
}

src/docs/reference/modules/Apis.GraphQL.Abstractions/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,26 @@ The following query gives us the results we want:
365365
}
366366
```
367367

368+
### Retrieve the parent content item using the List Part
369+
370+
When utilizing the List Part, you can also access the parent list content item. The following query demonstrates how to retrieve the parent blog content item for a blog post:
371+
372+
```json
373+
{
374+
blogPost {
375+
blog {
376+
listContentItem {
377+
... on Blog {
378+
displayText
379+
}
380+
}
381+
}
382+
}
383+
}
384+
```
385+
386+
This query will return the displayText of the parent blog content item associated with a specific blog post.
387+
368388
## More Info
369389

370390
For more information on GraphQL you can visit the following links:

test/OrchardCore.Tests/Apis/GraphQL/Blog/BlogPostTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,4 +284,20 @@ public async Task ShouldNotReturnBlogsWithoutViewBlogContentPermission()
284284

285285
Assert.Equal(GraphQLApi.ValidationRules.RequiresPermissionValidationRule.ErrorCode, result["errors"][0]["extensions"]["number"].ToString());
286286
}
287+
288+
[Fact]
289+
public async Task ShouldQueryContainedPart()
290+
{
291+
using var context = new BlogContext();
292+
await context.InitializeAsync();
293+
294+
var result = await context
295+
.GraphQLClient
296+
.Content
297+
.Query("blogPost { blog { listContentItem { ... on Blog { displayText } } } }");
298+
299+
Assert.Equal(
300+
"Blog",
301+
result["data"]["blogPost"][0]["blog"]["listContentItem"]["displayText"].ToString());
302+
}
287303
}

0 commit comments

Comments
 (0)