From cd1c0dc32165d9ef4c16a10979e41458ed318447 Mon Sep 17 00:00:00 2001 From: mdameer Date: Sat, 18 May 2024 00:04:13 +0300 Subject: [PATCH 01/29] Expose ContentFields as input fields, add more content definition events for updated type, parts and fields --- .../GraphQL/Fields/ContentFieldsProvider.cs | 50 ++++++++ .../Fields/ObjectGraphTypeFieldProvider.cs | 9 +- .../GraphQL/Startup.cs | 10 ++ .../Services/ContentDefinitionService.cs | 34 +++++- .../Queries}/Types/TimeSpanGraphType.cs | 2 +- .../Queries/WhereInputObjectGraphType.cs | 17 ++- .../Options/GraphQLContentOptions.cs | 2 +- ...chardCore.ContentManagement.GraphQL.csproj | 1 + .../Queries/Types/ContentItemWhereInput.cs | 14 ++- .../DynamicContentFieldsIndexAliasProvider.cs | 109 ++++++++++++++++++ .../Types/DynamicContentTypeBuilder.cs | 45 +++++++- .../Types/DynamicPartInputGraphType.cs | 35 ++++++ .../Queries/Types/IContentFieldProvider.cs | 3 + .../ServiceCollectionExtensions.cs | 11 ++ .../Events/ContentFieldUpdatedContext.cs | 4 + .../Events/ContentPartFieldUpdatedContext.cs | 4 + .../Events/ContentPartUpdatedContext.cs | 4 + .../Events/ContentTypePartUpdatedContext.cs | 4 + .../Events/ContentTypeUpdatedContext.cs | 4 + .../Events/IContentDefinitionEventHandler.cs | 5 + 20 files changed, 351 insertions(+), 16 deletions(-) rename src/{OrchardCore.Modules/OrchardCore.ContentFields/GraphQL => OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries}/Types/TimeSpanGraphType.cs (96%) create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs create mode 100644 src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentFieldUpdatedContext.cs create mode 100644 src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartFieldUpdatedContext.cs create mode 100644 src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartUpdatedContext.cs create mode 100644 src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypePartUpdatedContext.cs create mode 100644 src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypeUpdatedContext.cs diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index 53a00dfb719..9cda8969bea 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using GraphQL.Resolvers; using GraphQL.Types; +using OrchardCore.Apis.GraphQL.Queries.Types; using OrchardCore.ContentFields.Fields; using OrchardCore.ContentFields.GraphQL.Types; +using OrchardCore.ContentFields.Indexing.SQL; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.GraphQL.Queries.Types; using OrchardCore.ContentManagement.Metadata.Models; @@ -22,6 +24,11 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(BooleanGraphType), UnderlyingType = typeof(BooleanField), FieldAccessor = field => ((BooleanField)field).Value, + IndexDescriptor = new FieldTypeIndexDescriptor + { + IndexType = typeof(BooleanFieldIndex), + Index = nameof(BooleanFieldIndex.Boolean) + } } }, { @@ -32,6 +39,11 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(DateGraphType), UnderlyingType = typeof(DateField), FieldAccessor = field => ((DateField)field).Value, + IndexDescriptor = new FieldTypeIndexDescriptor + { + IndexType = typeof(DateFieldIndex), + Index = nameof(DateFieldIndex.Date) + } } }, { @@ -42,6 +54,11 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(DateTimeGraphType), UnderlyingType = typeof(DateTimeField), FieldAccessor = field => ((DateTimeField)field).Value, + IndexDescriptor = new FieldTypeIndexDescriptor + { + IndexType = typeof(DateTimeFieldIndex), + Index = nameof(DateTimeFieldIndex.DateTime) + } } }, { @@ -52,6 +69,11 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(DecimalGraphType), UnderlyingType = typeof(NumericField), FieldAccessor = field => ((NumericField)field).Value, + IndexDescriptor = new FieldTypeIndexDescriptor + { + IndexType = typeof(NumericFieldIndex), + Index = nameof(NumericFieldIndex.Numeric) + } } }, { @@ -62,6 +84,11 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(StringGraphType), UnderlyingType = typeof(TextField), FieldAccessor = field => ((TextField)field).Text, + IndexDescriptor = new FieldTypeIndexDescriptor + { + IndexType = typeof(TextFieldIndex), + Index = nameof(TextFieldIndex.Text) + } } }, { @@ -72,6 +99,11 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(TimeSpanGraphType), UnderlyingType = typeof(TimeField), FieldAccessor = field => ((TimeField)field).Value, + IndexDescriptor = new FieldTypeIndexDescriptor + { + IndexType = typeof(TimeFieldIndex), + Index = nameof(TimeFieldIndex.Time) + } } }, { @@ -118,12 +150,30 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech public bool HasField(ContentPartFieldDefinition field) => _contentFieldTypeMappings.ContainsKey(field.FieldDefinition.Name); + public (string, Type) GetFieldIndex(ContentPartFieldDefinition field) + { + if (!_contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldDescriptor) || + fieldDescriptor.IndexDescriptor == null) + { + return (null, null); + } + + return (fieldDescriptor.IndexDescriptor.Index, fieldDescriptor.IndexDescriptor.IndexType); + } + private sealed class FieldTypeDescriptor { public string Description { get; set; } public Type FieldType { get; set; } public Type UnderlyingType { get; set; } public Func FieldAccessor { get; set; } + public FieldTypeIndexDescriptor IndexDescriptor { get; set; } + } + + private sealed class FieldTypeIndexDescriptor + { + public Type IndexType { get; set; } + public string Index { get; set; } } } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index c88e173dce8..d9c528b02ca 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -34,7 +35,8 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech Type = queryGraphType.GetType(), Resolver = new FuncFieldResolver(context => { - var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType + .GetGenericArguments().First(); // Check if part has been collapsed by trying to get the parent part. ContentElement contentPart = context.Source.Get(field.PartDefinition.Name); @@ -51,6 +53,11 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech return null; } + public (string, Type) GetFieldIndex(ContentPartFieldDefinition field) + { + return (null, null); + } + private IObjectGraphType GetObjectGraphType(ContentPartFieldDefinition field) { var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs index 6eff7d9ab16..8b1e45c60e2 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Startup.cs @@ -3,6 +3,7 @@ using OrchardCore.ContentFields.Fields; using OrchardCore.ContentFields.GraphQL.Fields; using OrchardCore.ContentFields.GraphQL.Types; +using OrchardCore.ContentManagement.GraphQL; using OrchardCore.ContentManagement.GraphQL.Queries.Types; using OrchardCore.Modules; @@ -22,4 +23,13 @@ public override void ConfigureServices(IServiceCollection services) services.AddObjectGraphType(); } } + + [RequireFeatures("OrchardCore.Apis.GraphQL", "OrchardCore.ContentFields.Indexing.SQL")] + public class IndexStartup : StartupBase + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddContentFieldsInputGraphQL(); + } + } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentTypes/Services/ContentDefinitionService.cs b/src/OrchardCore.Modules/OrchardCore.ContentTypes/Services/ContentDefinitionService.cs index 21825fefe2a..d3c2baa9752 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentTypes/Services/ContentDefinitionService.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentTypes/Services/ContentDefinitionService.cs @@ -369,6 +369,12 @@ await _contentDefinitionManager.AlterPartDefinitionAsync(partViewModel.Name, par fieldBuilder.WithDisplayMode(fieldViewModel.DisplayMode); }); }); + + _contentDefinitionEventHandlers.Invoke((handler, context) => handler.ContentPartFieldUpdated(context), new ContentPartFieldUpdatedContext + { + ContentPartName = partViewModel.Name, + ContentFieldName = fieldViewModel.Name + }, _logger); } public async Task AlterTypePartAsync(EditTypePartViewModel typePartViewModel) @@ -385,10 +391,17 @@ await _contentDefinitionManager.AlterTypeDefinitionAsync(typeDefinition.Name, ty part.WithDisplayMode(typePartViewModel.DisplayMode); }); }); + + _contentDefinitionEventHandlers.Invoke((handler, context) => handler.ContentTypePartUpdated(context), new ContentTypePartUpdatedContext + { + ContentTypeName = typeDefinition.Name, + ContentPartName = typePartViewModel.Name + }, _logger); } - public Task AlterTypePartsOrderAsync(ContentTypeDefinition typeDefinition, string[] partNames) - => _contentDefinitionManager.AlterTypeDefinitionAsync(typeDefinition.Name, type => + public async Task AlterTypePartsOrderAsync(ContentTypeDefinition typeDefinition, string[] partNames) + { + await _contentDefinitionManager.AlterTypeDefinitionAsync(typeDefinition.Name, type => { if (partNames is null) { @@ -411,8 +424,15 @@ public Task AlterTypePartsOrderAsync(ContentTypeDefinition typeDefinition, strin } }); - public Task AlterPartFieldsOrderAsync(ContentPartDefinition partDefinition, string[] fieldNames) - => _contentDefinitionManager.AlterPartDefinitionAsync(partDefinition.Name, type => + _contentDefinitionEventHandlers.Invoke((handler, context) => handler.ContentTypeUpdated(context), new ContentTypeUpdatedContext + { + ContentTypeDefinition = typeDefinition + }, _logger); + } + + public async Task AlterPartFieldsOrderAsync(ContentPartDefinition partDefinition, string[] fieldNames) + { + await _contentDefinitionManager.AlterPartDefinitionAsync(partDefinition.Name, type => { if (fieldNames is null) { @@ -429,6 +449,12 @@ public Task AlterPartFieldsOrderAsync(ContentPartDefinition partDefinition, stri } }); + _contentDefinitionEventHandlers.Invoke((handler, context) => handler.ContentPartUpdated(context), new ContentPartUpdatedContext + { + ContentPartDefinition = partDefinition + }, _logger); + } + public async Task GenerateContentTypeNameFromDisplayNameAsync(string displayName) { displayName = displayName.ToSafeName(); diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/TimeSpanGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/Types/TimeSpanGraphType.cs similarity index 96% rename from src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/TimeSpanGraphType.cs rename to src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/Types/TimeSpanGraphType.cs index a528e3fd9a1..fbe1721c48f 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Types/TimeSpanGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/Types/TimeSpanGraphType.cs @@ -3,7 +3,7 @@ using GraphQL.Types; using GraphQLParser.AST; -namespace OrchardCore.ContentFields.GraphQL.Types +namespace OrchardCore.Apis.GraphQL.Queries.Types { public class TimeSpanGraphType : ScalarGraphType { diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs index 0a53026bc97..d49e72f16de 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using GraphQL.Types; +using OrchardCore.Apis.GraphQL.Queries.Types; namespace OrchardCore.Apis.GraphQL.Queries { @@ -58,6 +59,12 @@ public void AddScalarFilterFields(string fieldName, string descripti public void AddScalarFilterFields(Type graphType, string fieldName, string description) { + if (!typeof(ScalarGraphType).IsAssignableFrom(graphType) && + !typeof(IInputObjectGraphType).IsAssignableFrom(graphType)) + { + return; + } + AddEqualityFilters(graphType, fieldName, description); AddMultiValueFilters(graphType, fieldName, description); @@ -65,7 +72,15 @@ public void AddScalarFilterFields(Type graphType, string fieldName, string descr { AddStringFilters(graphType, fieldName, description); } - else if (graphType == typeof(DateTimeGraphType)) + else if (graphType == typeof(DateTimeGraphType) || + graphType == typeof(DateGraphType) || + graphType == typeof(DateOnlyGraphType) || + graphType == typeof(TimeSpanGraphType) || + graphType == typeof(DecimalGraphType) || + graphType == typeof(IntGraphType) || + graphType == typeof(LongGraphType) || + graphType == typeof(FloatGraphType) || + graphType == typeof(BigIntGraphType)) { AddNonStringFilters(graphType, fieldName, description); } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs index ac2da4221c3..bd494b4073a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Options/GraphQLContentOptions.cs @@ -232,7 +232,7 @@ internal static string GetFieldName(ContentTypePartDefinition definition, string return partName.ToFieldName() + fieldName.ToPascalCase(); } - return fieldName; + return fieldName.ToCamelCase(); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/OrchardCore.ContentManagement.GraphQL.csproj b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/OrchardCore.ContentManagement.GraphQL.csproj index 22ba7187d4e..707c1049ee5 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/OrchardCore.ContentManagement.GraphQL.csproj +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/OrchardCore.ContentManagement.GraphQL.csproj @@ -17,6 +17,7 @@ + diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs index 25c52b56d69..d1fe51f06f2 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs @@ -1,3 +1,4 @@ +using System; using GraphQL.Types; using Microsoft.Extensions.Options; using OrchardCore.Apis.GraphQL; @@ -36,12 +37,19 @@ public ContentItemWhereInput(string contentItemName, IOptions>("Not").Description("NOT logical operation").Type(whereInputType); } - private void AddFilterField(string name, string description) + public void AddFilterField(string name, string description) { - if (!_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), name)) + AddFilterField(typeof(TGraphType), name, description); + } + + public void AddFilterField(Type graphType, string name, string description) + { + if (_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), name)) { - AddScalarFilterFields(name, description); + return; } + + AddScalarFilterFields(graphType, name, description); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs new file mode 100644 index 00000000000..8f5d0240bbe --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GraphQL; +using Microsoft.Extensions.Options; +using OrchardCore.ContentManagement.GraphQL.Options; +using OrchardCore.ContentManagement.Metadata; +using OrchardCore.ContentTypes.Events; + +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; + +public class DynamicContentFieldsIndexAliasProvider : IIndexAliasProvider, IContentDefinitionEventHandler +{ + private static readonly List _aliases = new List(); + + private readonly IContentDefinitionManager _contentDefinitionManager; + private readonly IEnumerable _contentFieldProviders; + private readonly GraphQLContentOptions _contentOptions; + + public DynamicContentFieldsIndexAliasProvider(IContentDefinitionManager contentDefinitionManager, + IEnumerable contentFieldProviders, + IOptions contentOptionsAccessor) + { + _contentDefinitionManager = contentDefinitionManager; + _contentFieldProviders = contentFieldProviders; + _contentOptions = contentOptionsAccessor.Value; + } + + public IEnumerable GetAliases() + { + if (_aliases.Count != 0) + { + return _aliases; + } + + var types = _contentDefinitionManager.ListTypeDefinitionsAsync().GetAwaiter().GetResult(); + var parts = types.SelectMany(t => t.Parts); + + foreach (var part in parts) + { + foreach (var field in part.PartDefinition.Fields) + { + string alias = _contentOptions.ShouldCollapse(part) ? + GraphQLContentOptions.GetFieldName(part, part.Name, field.Name) : + $"{field.PartDefinition.Name.ToFieldName()}.{field.Name.ToCamelCase()}"; + + foreach (var fieldProvider in _contentFieldProviders) + { + var (index, indexType) = fieldProvider.GetFieldIndex(field); + + if (indexType == null || index == null) + { + continue; + } + + _aliases.Add(new IndexAlias + { + Alias = alias, + Index = index, + IndexType = indexType + }); + + break; + } + } + } + + return _aliases; + } + + private static void ClearAliases() + { + _aliases.Clear(); + } + + public void ContentFieldAttached(ContentFieldAttachedContext context) => ClearAliases(); + + public void ContentFieldDetached(ContentFieldDetachedContext context) => ClearAliases(); + + public void ContentPartAttached(ContentPartAttachedContext context) => ClearAliases(); + + public void ContentPartCreated(ContentPartCreatedContext context) => ClearAliases(); + + public void ContentPartDetached(ContentPartDetachedContext context) => ClearAliases(); + + public void ContentPartImported(ContentPartImportedContext context) => ClearAliases(); + + public void ContentPartImporting(ContentPartImportingContext context) { } + + public void ContentPartRemoved(ContentPartRemovedContext context) => ClearAliases(); + + public void ContentTypeCreated(ContentTypeCreatedContext context) => ClearAliases(); + + public void ContentTypeImported(ContentTypeImportedContext context) => ClearAliases(); + + public void ContentTypeImporting(ContentTypeImportingContext context) { } + + public void ContentTypeRemoved(ContentTypeRemovedContext context) => ClearAliases(); + + public void ContentTypeUpdated(ContentTypeUpdatedContext context) => ClearAliases(); + + public void ContentPartUpdated(ContentPartUpdatedContext context) => ClearAliases(); + + public void ContentTypePartUpdated(ContentTypePartUpdatedContext context) => ClearAliases(); + + public void ContentFieldUpdated(ContentFieldUpdatedContext context) => ClearAliases(); + + public void ContentPartFieldUpdated(ContentPartFieldUpdatedContext context) => ClearAliases(); +} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index b699530f66c..7b88a33646d 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -12,20 +12,29 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { public class DynamicContentTypeBuilder : IContentTypeBuilder { + // If registered, then fields input should be exposed, otherwise ignored. + public class FieldsInputMarker + { + } + private readonly IHttpContextAccessor _httpContextAccessor; private readonly GraphQLContentOptions _contentOptions; protected readonly IStringLocalizer S; + private readonly FieldsInputMarker _fieldsInputMarker; + private readonly Dictionary _dynamicPartFields; public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, - IStringLocalizer localizer) + IStringLocalizer localizer, + FieldsInputMarker fieldsInputMarker = null) { _httpContextAccessor = httpContextAccessor; _contentOptions = contentOptionsAccessor.Value; _dynamicPartFields = []; S = localizer; + _fieldsInputMarker = fieldsInputMarker; } public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) @@ -33,6 +42,8 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; var contentFieldProviders = serviceProvider.GetServices().ToList(); + var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; + if (_contentOptions.ShouldHide(contentTypeDefinition)) { return; @@ -53,7 +64,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin continue; } - if (!(part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(field))))) + if (!part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(field)))) { continue; } @@ -76,6 +87,11 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin } contentItemType.AddField(fieldType); + + if (_fieldsInputMarker != null) + { + whereInputType.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); + } break; } } @@ -97,16 +113,25 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin if (contentFieldType != null && !contentItemType.HasField(contentFieldType.Name)) { contentItemType.AddField(contentFieldType); + + if (_fieldsInputMarker != null) + { + whereInputType.AddFilterField(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); + } + break; } } } - continue; } - - if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) + else if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) { contentItemType.AddField(fieldType); + + if (_fieldsInputMarker != null) + { + whereInputType.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); + } } else { @@ -122,6 +147,16 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin }); field.Type(new DynamicPartGraphType(_httpContextAccessor, part)); + + if (_fieldsInputMarker != null) + { + var inputField = whereInputType + .Field(partName.ToFieldName()) + .Description(S["Represents a {0}.", part.PartDefinition.Name]); + + inputField.Type(new DynamicPartInputGraphType(_httpContextAccessor, part)); + } + _dynamicPartFields[partName] = field.FieldType; } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs new file mode 100644 index 00000000000..8501a2e075e --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs @@ -0,0 +1,35 @@ +using System.Linq; +using GraphQL.Types; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Apis.GraphQL.Queries; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; + +public sealed class DynamicPartInputGraphType : WhereInputObjectGraphType +{ + public DynamicPartInputGraphType(IHttpContextAccessor httpContextAccessor, ContentTypePartDefinition part) + { + Name = $"{part.Name}WhereInput"; + + var serviceProvider = httpContextAccessor.HttpContext.RequestServices; + var contentFieldProviders = serviceProvider.GetServices().ToList(); + + foreach (var field in part.PartDefinition.Fields) + { + foreach (var fieldProvider in contentFieldProviders) + { + var fieldType = fieldProvider.GetField(field, part.Name); + + if (fieldType == null) + { + continue; + } + + AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); + break; + } + } + } +} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs index addbb2a00b7..250d79e705f 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs @@ -1,3 +1,4 @@ +using System; using GraphQL.Types; using OrchardCore.ContentManagement.Metadata.Models; @@ -8,5 +9,7 @@ public interface IContentFieldProvider FieldType GetField(ContentPartFieldDefinition field, string namedPartTechnicalName, string customFieldName = null); bool HasField(ContentPartFieldDefinition field); + + (string, Type) GetFieldIndex(ContentPartFieldDefinition field); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs index a88b7a07230..4f3c3189d12 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using OrchardCore.ContentManagement.GraphQL.Queries; using OrchardCore.ContentManagement.GraphQL.Queries.Types; using OrchardCore.ContentManagement.Records; +using OrchardCore.ContentTypes.Events; using OrchardCore.Security.Permissions; using YesSql.Indexes; @@ -32,6 +33,16 @@ public static IServiceCollection AddContentGraphQL(this IServiceCollection servi return services; } + public static IServiceCollection AddContentFieldsInputGraphQL(this IServiceCollection services) + { + services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + public static void AddWhereInputIndexPropertyProvider(this IServiceCollection services) where TIndexType : MapIndex { diff --git a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentFieldUpdatedContext.cs b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentFieldUpdatedContext.cs new file mode 100644 index 00000000000..795547cd5ab --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentFieldUpdatedContext.cs @@ -0,0 +1,4 @@ +namespace OrchardCore.ContentTypes.Events +{ + public class ContentFieldUpdatedContext : ContentPartFieldContext { } +} diff --git a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartFieldUpdatedContext.cs b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartFieldUpdatedContext.cs new file mode 100644 index 00000000000..c6ac4484125 --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartFieldUpdatedContext.cs @@ -0,0 +1,4 @@ +namespace OrchardCore.ContentTypes.Events +{ + public class ContentPartFieldUpdatedContext : ContentPartFieldContext { } +} diff --git a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartUpdatedContext.cs b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartUpdatedContext.cs new file mode 100644 index 00000000000..9e67731700c --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentPartUpdatedContext.cs @@ -0,0 +1,4 @@ +namespace OrchardCore.ContentTypes.Events +{ + public class ContentPartUpdatedContext : ContentPartContext { } +} diff --git a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypePartUpdatedContext.cs b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypePartUpdatedContext.cs new file mode 100644 index 00000000000..467821f7fcc --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypePartUpdatedContext.cs @@ -0,0 +1,4 @@ +namespace OrchardCore.ContentTypes.Events +{ + public class ContentTypePartUpdatedContext : ContentTypePartContext { } +} diff --git a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypeUpdatedContext.cs b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypeUpdatedContext.cs new file mode 100644 index 00000000000..d9d9c15ca7f --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/ContentTypeUpdatedContext.cs @@ -0,0 +1,4 @@ +namespace OrchardCore.ContentTypes.Events +{ + public class ContentTypeUpdatedContext : ContentTypeContext { } +} diff --git a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/IContentDefinitionEventHandler.cs b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/IContentDefinitionEventHandler.cs index a0ee47f5c4b..11f3b47e348 100644 --- a/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/IContentDefinitionEventHandler.cs +++ b/src/OrchardCore/OrchardCore.ContentTypes.Abstractions/Events/IContentDefinitionEventHandler.cs @@ -3,16 +3,21 @@ namespace OrchardCore.ContentTypes.Events public interface IContentDefinitionEventHandler { void ContentTypeCreated(ContentTypeCreatedContext context); + void ContentTypeUpdated(ContentTypeUpdatedContext context); void ContentTypeRemoved(ContentTypeRemovedContext context); void ContentTypeImporting(ContentTypeImportingContext context); void ContentTypeImported(ContentTypeImportedContext context); void ContentPartCreated(ContentPartCreatedContext context); + void ContentPartUpdated(ContentPartUpdatedContext context); void ContentPartRemoved(ContentPartRemovedContext context); void ContentPartAttached(ContentPartAttachedContext context); void ContentPartDetached(ContentPartDetachedContext context); void ContentPartImporting(ContentPartImportingContext context); void ContentPartImported(ContentPartImportedContext context); + void ContentTypePartUpdated(ContentTypePartUpdatedContext context); void ContentFieldAttached(ContentFieldAttachedContext context); + void ContentFieldUpdated(ContentFieldUpdatedContext context); void ContentFieldDetached(ContentFieldDetachedContext context); + void ContentPartFieldUpdated(ContentPartFieldUpdatedContext context); } } From 9d132e8a2e6bfe378eff0c3362cdc6ba06e10a55 Mon Sep 17 00:00:00 2001 From: mdameer Date: Sat, 18 May 2024 02:17:28 +0300 Subject: [PATCH 02/29] Exclude empty input parts --- .../GraphQL/Fields/ContentFieldsProvider.cs | 8 ++++++-- .../GraphQL/Fields/ObjectGraphTypeFieldProvider.cs | 2 ++ .../Types/DynamicContentFieldsIndexAliasProvider.cs | 6 +++--- .../Queries/Types/DynamicContentTypeBuilder.cs | 8 ++++---- .../Queries/Types/IContentFieldProvider.cs | 2 ++ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index 9cda8969bea..7ea6153bc2f 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -152,15 +152,19 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech public (string, Type) GetFieldIndex(ContentPartFieldDefinition field) { - if (!_contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldDescriptor) || - fieldDescriptor.IndexDescriptor == null) + if (!HasFieldIndex(field)) { return (null, null); } + var fieldDescriptor = _contentFieldTypeMappings[field.FieldDefinition.Name]; return (fieldDescriptor.IndexDescriptor.Index, fieldDescriptor.IndexDescriptor.IndexType); } + public bool HasFieldIndex(ContentPartFieldDefinition field) => + _contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldTypeDescriptor) && + fieldTypeDescriptor.IndexDescriptor != null; + private sealed class FieldTypeDescriptor { public string Description { get; set; } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index d9c528b02ca..adb169c5c3d 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -69,5 +69,7 @@ private IObjectGraphType GetObjectGraphType(ContentPartFieldDefinition field) } public bool HasField(ContentPartFieldDefinition field) => GetObjectGraphType(field) != null; + + public bool HasFieldIndex(ContentPartFieldDefinition field) => false; } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 8f5d0240bbe..1922f57af53 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -46,13 +46,13 @@ public IEnumerable GetAliases() foreach (var fieldProvider in _contentFieldProviders) { - var (index, indexType) = fieldProvider.GetFieldIndex(field); - - if (indexType == null || index == null) + if (!fieldProvider.HasFieldIndex(field)) { continue; } + var (index, indexType) = fieldProvider.GetFieldIndex(field); + _aliases.Add(new IndexAlias { Alias = alias, diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 7b88a33646d..3c06ca6df4e 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -88,7 +88,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin contentItemType.AddField(fieldType); - if (_fieldsInputMarker != null) + if (_fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) { whereInputType.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); } @@ -114,7 +114,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin { contentItemType.AddField(contentFieldType); - if (_fieldsInputMarker != null) + if (_fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) { whereInputType.AddFilterField(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); } @@ -148,11 +148,11 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin field.Type(new DynamicPartGraphType(_httpContextAccessor, part)); - if (_fieldsInputMarker != null) + if (_fieldsInputMarker != null && part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) { var inputField = whereInputType .Field(partName.ToFieldName()) - .Description(S["Represents a {0}.", part.PartDefinition.Name]); + .Description(S["Represents a {0} input.", part.PartDefinition.Name]); inputField.Type(new DynamicPartInputGraphType(_httpContextAccessor, part)); } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs index 250d79e705f..72c8db12935 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs @@ -11,5 +11,7 @@ public interface IContentFieldProvider bool HasField(ContentPartFieldDefinition field); (string, Type) GetFieldIndex(ContentPartFieldDefinition field); + + bool HasFieldIndex(ContentPartFieldDefinition field); } } From 44a202f7a8b5f1eb8350e06b3ac849ab23b28b9a Mon Sep 17 00:00:00 2001 From: mdameer Date: Sat, 18 May 2024 23:50:54 +0300 Subject: [PATCH 03/29] Remove unused usings, fix injecting for nullable dependency "fieldsInputMarker" --- .../DynamicContentFieldsIndexAliasProvider.cs | 1 - .../Types/DynamicContentTypeBuilder.cs | 19 +++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 1922f57af53..cc69e248b03 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using GraphQL; using Microsoft.Extensions.Options; using OrchardCore.ContentManagement.GraphQL.Options; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 3c06ca6df4e..9bc2891d295 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -20,27 +20,24 @@ public class FieldsInputMarker private readonly IHttpContextAccessor _httpContextAccessor; private readonly GraphQLContentOptions _contentOptions; protected readonly IStringLocalizer S; - private readonly FieldsInputMarker _fieldsInputMarker; - private readonly Dictionary _dynamicPartFields; public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, - IStringLocalizer localizer, - FieldsInputMarker fieldsInputMarker = null) + IStringLocalizer localizer) { _httpContextAccessor = httpContextAccessor; _contentOptions = contentOptionsAccessor.Value; _dynamicPartFields = []; S = localizer; - _fieldsInputMarker = fieldsInputMarker; } public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; var contentFieldProviders = serviceProvider.GetServices().ToList(); + var fieldsInputMarker = serviceProvider.GetService(); var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; @@ -88,7 +85,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin contentItemType.AddField(fieldType); - if (_fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) + if (whereInputType != null && fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) { whereInputType.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); } @@ -114,7 +111,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin { contentItemType.AddField(contentFieldType); - if (_fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) + if (whereInputType != null && fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) { whereInputType.AddFilterField(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); } @@ -128,9 +125,9 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin { contentItemType.AddField(fieldType); - if (_fieldsInputMarker != null) + if (whereInputType != null && fieldsInputMarker != null) { - whereInputType.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); + whereInputType?.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); } } else @@ -148,7 +145,9 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin field.Type(new DynamicPartGraphType(_httpContextAccessor, part)); - if (_fieldsInputMarker != null && part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) + if (whereInputType != null && + fieldsInputMarker != null && + part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) { var inputField = whereInputType .Field(partName.ToFieldName()) From d8fa1cc435a61f12940022bfc80fb57ff162cbfe Mon Sep 17 00:00:00 2001 From: mdameer Date: Mon, 20 May 2024 21:15:43 +0300 Subject: [PATCH 04/29] Fix multi tenant issue with dynamic index alias provider --- .../DynamicContentFieldsIndexAliasProvider.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index cc69e248b03..30f823f2f0e 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using GraphQL; @@ -5,31 +6,37 @@ using OrchardCore.ContentManagement.GraphQL.Options; using OrchardCore.ContentManagement.Metadata; using OrchardCore.ContentTypes.Events; +using OrchardCore.Environment.Shell; namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; public class DynamicContentFieldsIndexAliasProvider : IIndexAliasProvider, IContentDefinitionEventHandler { - private static readonly List _aliases = new List(); + private static readonly ConcurrentDictionary> _aliases = new ConcurrentDictionary>(); private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IEnumerable _contentFieldProviders; + private readonly ShellSettings _shellSettings; private readonly GraphQLContentOptions _contentOptions; public DynamicContentFieldsIndexAliasProvider(IContentDefinitionManager contentDefinitionManager, IEnumerable contentFieldProviders, - IOptions contentOptionsAccessor) + IOptions contentOptionsAccessor, + ShellSettings shellSettings) { _contentDefinitionManager = contentDefinitionManager; _contentFieldProviders = contentFieldProviders; _contentOptions = contentOptionsAccessor.Value; + _shellSettings = shellSettings; } public IEnumerable GetAliases() { - if (_aliases.Count != 0) + var tenantAliases = _aliases.GetOrAdd(_shellSettings.Name, _ => []); + + if (tenantAliases.Count != 0) { - return _aliases; + return tenantAliases; } var types = _contentDefinitionManager.ListTypeDefinitionsAsync().GetAwaiter().GetResult(); @@ -51,8 +58,8 @@ public IEnumerable GetAliases() } var (index, indexType) = fieldProvider.GetFieldIndex(field); - - _aliases.Add(new IndexAlias + + tenantAliases.Add(new IndexAlias { Alias = alias, Index = index, @@ -64,12 +71,12 @@ public IEnumerable GetAliases() } } - return _aliases; + return tenantAliases; } - private static void ClearAliases() + private void ClearAliases() { - _aliases.Clear(); + _aliases.GetOrAdd(_shellSettings.Name, _ => []).Clear(); } public void ContentFieldAttached(ContentFieldAttachedContext context) => ClearAliases(); From 387ea80efd440bd7048268474beaefc6e223e2fb Mon Sep 17 00:00:00 2001 From: mdameer Date: Fri, 24 May 2024 00:43:09 +0300 Subject: [PATCH 05/29] Fix duplicate aliases --- .../Queries/ContentItemsFieldType.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 484494cf969..67148895a12 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -143,8 +143,10 @@ private IQuery FilterWhereArguments( foreach (var alias in aliasProvider.GetAliases()) { predicateQuery.CreateAlias(alias.Alias, alias.Index); - indexAliases.Add(alias.Alias, alias.Alias); - indexes.TryAdd(alias.Index, alias); + if (indexAliases.TryAdd(alias.Alias, alias.Alias)) + { + indexes.TryAdd(alias.Index, alias); + } } } From 6d88f14ab8176f6e7ba76cb5e1a6e31b5fa363a9 Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 28 May 2024 01:10:05 +0300 Subject: [PATCH 06/29] Differentiate query and input builders for the dynamic content item --- .../Queries/WhereInputObjectGraphType.cs | 5 + .../Queries/ContentItemsFieldType.cs | 2 +- .../Queries/Types/ContentItemWhereInput.cs | 18 +-- .../Types/DynamicContentTypeBuilder.cs | 109 +++++++++--------- .../Types/DynamicContentTypeInputBuilder.cs | 30 +++++ .../Types/DynamicContentTypeQueryBuilder.cs | 24 ++++ .../Types/DynamicPartInputGraphType.cs | 3 +- .../ServiceCollectionExtensions.cs | 4 +- 8 files changed, 124 insertions(+), 71 deletions(-) create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs index d49e72f16de..45c53844807 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs @@ -52,6 +52,11 @@ public override object ParseDictionary(IDictionary value) {"_not_ends_with", "does not end with the string"}, }; + public void AddScalarFilterFields(FieldType fieldType) + { + AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); + } + public void AddScalarFilterFields(string fieldName, string description) { AddScalarFilterFields(typeof(TGraphType), fieldName, description); diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 7b3b47b6522..bbfab670a56 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -283,7 +283,7 @@ private void BuildExpressionsInternal(JsonObject where, Junction expressions, st if (whereArgument != null) { - var whereInput = (WhereInputObjectGraphType)whereArgument.ResolvedType; + var whereInput = (WhereInputObjectGraphType)whereArgument.ResolvedType; foreach (var field in whereInput.Fields.Where(x => x.GetMetadata("PartName") != null)) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs index d1fe51f06f2..0979467ae08 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs @@ -1,4 +1,3 @@ -using System; using GraphQL.Types; using Microsoft.Extensions.Options; using OrchardCore.Apis.GraphQL; @@ -10,7 +9,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types [GraphQLFieldName("Or", "OR")] [GraphQLFieldName("And", "AND")] [GraphQLFieldName("Not", "NOT")] - public class ContentItemWhereInput : WhereInputObjectGraphType + public class ContentItemWhereInput : WhereInputObjectGraphType { private readonly IOptions _optionsAccessor; @@ -37,19 +36,12 @@ public ContentItemWhereInput(string contentItemName, IOptions>("Not").Description("NOT logical operation").Type(whereInputType); } - public void AddFilterField(string name, string description) + private void AddFilterField(string name, string description) { - AddFilterField(typeof(TGraphType), name, description); - } - - public void AddFilterField(Type graphType, string name, string description) - { - if (_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), name)) + if (!_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), name)) { - return; + AddScalarFilterFields(name, description); } - - AddScalarFilterFields(graphType, name, description); } } -} +} \ No newline at end of file diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 9bc2891d295..6ddde2a6e2a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -2,45 +2,39 @@ using System.Linq; using GraphQL.Types; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; +using OrchardCore.Apis.GraphQL.Queries; using OrchardCore.ContentManagement.GraphQL.Options; using OrchardCore.ContentManagement.Metadata.Models; namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { - public class DynamicContentTypeBuilder : IContentTypeBuilder + public abstract class DynamicContentTypeBuilder : IContentTypeBuilder { - // If registered, then fields input should be exposed, otherwise ignored. - public class FieldsInputMarker - { - } - - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly GraphQLContentOptions _contentOptions; + protected readonly IHttpContextAccessor _httpContextAccessor; + protected readonly IEnumerable _contentFieldProviders; + protected readonly GraphQLContentOptions _contentOptions; protected readonly IStringLocalizer S; private readonly Dictionary _dynamicPartFields; public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, + IEnumerable contentFieldProviders, IOptions contentOptionsAccessor, IStringLocalizer localizer) { _httpContextAccessor = httpContextAccessor; + _contentFieldProviders = contentFieldProviders; _contentOptions = contentOptionsAccessor.Value; _dynamicPartFields = []; S = localizer; } - public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) - { - var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; - var contentFieldProviders = serviceProvider.GetServices().ToList(); - var fieldsInputMarker = serviceProvider.GetService(); - - var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; + public abstract void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); + public void BuildInternal(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ComplexGraphType graphType) + { if (_contentOptions.ShouldHide(contentTypeDefinition)) { return; @@ -61,7 +55,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin continue; } - if (!part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(field)))) + if (!part.PartDefinition.Fields.Any(field => _contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(field)))) { continue; } @@ -70,7 +64,7 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin { foreach (var field in part.PartDefinition.Fields) { - foreach (var fieldProvider in contentFieldProviders) + foreach (var fieldProvider in _contentFieldProviders) { var customFieldName = GraphQLContentOptions.GetFieldName(part, part.Name, field.Name); @@ -83,12 +77,18 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin continue; } - contentItemType.AddField(fieldType); - - if (whereInputType != null && fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) + if (graphType is WhereInputObjectGraphType whereGraphType) + { + if (fieldProvider.HasFieldIndex(field)) + { + whereGraphType.AddScalarFilterFields(fieldType); + } + } + else { - whereInputType.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); + graphType.AddField(fieldType); } + break; } } @@ -97,23 +97,28 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin else { // Check if another builder has already added a field for this part. - var existingField = contentItemType.GetField(partName.ToFieldName()); + var existingField = graphType.GetField(partName.ToFieldName()); if (existingField != null) { // Add content field types. foreach (var field in part.PartDefinition.Fields) { - foreach (var fieldProvider in contentFieldProviders) + foreach (var fieldProvider in _contentFieldProviders) { var contentFieldType = fieldProvider.GetField(field, part.Name); - if (contentFieldType != null && !contentItemType.HasField(contentFieldType.Name)) + if (contentFieldType != null && !graphType.HasField(contentFieldType.Name)) { - contentItemType.AddField(contentFieldType); - - if (whereInputType != null && fieldsInputMarker != null && fieldProvider.HasFieldIndex(field)) + if (graphType is WhereInputObjectGraphType whereGraphType) + { + if (fieldProvider.HasFieldIndex(field)) + { + whereGraphType.AddScalarFilterFields(contentFieldType); + } + } + else { - whereInputType.AddFilterField(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); + graphType.AddField(contentFieldType); } break; @@ -123,40 +128,38 @@ public void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefin } else if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) { - contentItemType.AddField(fieldType); - - if (whereInputType != null && fieldsInputMarker != null) + if (graphType is WhereInputObjectGraphType whereGraphType) + { + whereGraphType.AddScalarFilterFields(fieldType); + } + else { - whereInputType?.AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); + graphType.AddField(fieldType); } } else { - var field = contentItemType - .Field(partName.ToFieldName()) - .Description(S["Represents a {0}.", part.PartDefinition.Name]) - .Resolve(context => + if (graphType is WhereInputObjectGraphType whereGraphType) + { + if (part.PartDefinition.Fields.Any(field => _contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) { - var nameToResolve = partName; - var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); - - return context.Source.Get(typeToResolve, nameToResolve); - }); - - field.Type(new DynamicPartGraphType(_httpContextAccessor, part)); + var inputField = whereGraphType + .Field(partName.ToFieldName()) + .Description(S["Represents a {0} input.", part.PartDefinition.Name]); - if (whereInputType != null && - fieldsInputMarker != null && - part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) + inputField.Type(new DynamicPartInputGraphType(_httpContextAccessor, part)); + _dynamicPartFields[partName] = inputField.FieldType; + } + } + else { - var inputField = whereInputType - .Field(partName.ToFieldName()) - .Description(S["Represents a {0} input.", part.PartDefinition.Name]); + var field = graphType + .Field(partName.ToFieldName()) + .Description(S["Represents a {0}.", part.PartDefinition.Name]); - inputField.Type(new DynamicPartInputGraphType(_httpContextAccessor, part)); + field.Type(new DynamicPartGraphType(_httpContextAccessor, part)); + _dynamicPartFields[partName] = field.FieldType; } - - _dynamicPartFields[partName] = field.FieldType; } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs new file mode 100644 index 00000000000..b72c1cf8893 --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using GraphQL.Types; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using OrchardCore.ContentManagement.GraphQL.Options; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types +{ + public class DynamicContentTypeInputBuilder : DynamicContentTypeBuilder + { + public DynamicContentTypeInputBuilder(IHttpContextAccessor httpContextAccessor, + IEnumerable contentFieldProviders, + IOptions contentOptionsAccessor, + IStringLocalizer localizer) + : base(httpContextAccessor, contentFieldProviders, contentOptionsAccessor, localizer) { } + + public override void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + { + var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; + + if (whereInputType != null) + { + BuildInternal(contentQuery, contentTypeDefinition, whereInputType); + } + } + } +} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs new file mode 100644 index 00000000000..21d81cd3f9c --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using GraphQL.Types; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using OrchardCore.ContentManagement.GraphQL.Options; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types +{ + public class DynamicContentTypeQueryBuilder : DynamicContentTypeBuilder + { + public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, + IEnumerable contentFieldProviders, + IOptions contentOptionsAccessor, + IStringLocalizer localizer) + : base(httpContextAccessor, contentFieldProviders, contentOptionsAccessor, localizer) { } + + public override void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + { + BuildInternal(contentQuery, contentTypeDefinition, contentItemType); + } + } +} \ No newline at end of file diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs index 8501a2e075e..61b960c6fc7 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs @@ -1,5 +1,4 @@ using System.Linq; -using GraphQL.Types; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using OrchardCore.Apis.GraphQL.Queries; @@ -26,7 +25,7 @@ public DynamicPartInputGraphType(IHttpContextAccessor httpContextAccessor, Conte { continue; } - + AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); break; } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs index 4f3c3189d12..f72087cb66a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs @@ -24,7 +24,7 @@ public static IServiceCollection AddContentGraphQL(this IServiceCollection servi services.AddTransient(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddOptions(); services.AddGraphQLFilterType(); @@ -37,7 +37,7 @@ public static IServiceCollection AddContentFieldsInputGraphQL(this IServiceColle { services.AddTransient(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); return services; From 06680e5d71663ddbbd015439f9c6e9d67c89fb4d Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 28 May 2024 09:34:02 +0300 Subject: [PATCH 07/29] Return "FieldTypeIndexDescriptor" instead of tuple from GetFieldIndex --- .../GraphQL/Fields/ContentFieldsProvider.cs | 64 +++++++------------ .../Fields/ObjectGraphTypeFieldProvider.cs | 5 +- .../DynamicContentFieldsIndexAliasProvider.cs | 6 +- .../Queries/Types/IContentFieldProvider.cs | 8 ++- 4 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs index 7ea6153bc2f..091fc733820 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ContentFieldsProvider.cs @@ -4,7 +4,6 @@ using GraphQL.Types; using OrchardCore.Apis.GraphQL.Queries.Types; using OrchardCore.ContentFields.Fields; -using OrchardCore.ContentFields.GraphQL.Types; using OrchardCore.ContentFields.Indexing.SQL; using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.GraphQL.Queries.Types; @@ -24,11 +23,8 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(BooleanGraphType), UnderlyingType = typeof(BooleanField), FieldAccessor = field => ((BooleanField)field).Value, - IndexDescriptor = new FieldTypeIndexDescriptor - { - IndexType = typeof(BooleanFieldIndex), - Index = nameof(BooleanFieldIndex.Boolean) - } + IndexType = typeof(BooleanFieldIndex), + Index = nameof(BooleanFieldIndex.Boolean) } }, { @@ -39,11 +35,8 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(DateGraphType), UnderlyingType = typeof(DateField), FieldAccessor = field => ((DateField)field).Value, - IndexDescriptor = new FieldTypeIndexDescriptor - { - IndexType = typeof(DateFieldIndex), - Index = nameof(DateFieldIndex.Date) - } + IndexType = typeof(DateFieldIndex), + Index = nameof(DateFieldIndex.Date) } }, { @@ -54,11 +47,8 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(DateTimeGraphType), UnderlyingType = typeof(DateTimeField), FieldAccessor = field => ((DateTimeField)field).Value, - IndexDescriptor = new FieldTypeIndexDescriptor - { - IndexType = typeof(DateTimeFieldIndex), - Index = nameof(DateTimeFieldIndex.DateTime) - } + IndexType = typeof(DateTimeFieldIndex), + Index = nameof(DateTimeFieldIndex.DateTime) } }, { @@ -69,11 +59,8 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(DecimalGraphType), UnderlyingType = typeof(NumericField), FieldAccessor = field => ((NumericField)field).Value, - IndexDescriptor = new FieldTypeIndexDescriptor - { - IndexType = typeof(NumericFieldIndex), - Index = nameof(NumericFieldIndex.Numeric) - } + IndexType = typeof(NumericFieldIndex), + Index = nameof(NumericFieldIndex.Numeric) } }, { @@ -84,11 +71,8 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(StringGraphType), UnderlyingType = typeof(TextField), FieldAccessor = field => ((TextField)field).Text, - IndexDescriptor = new FieldTypeIndexDescriptor - { - IndexType = typeof(TextFieldIndex), - Index = nameof(TextFieldIndex.Text) - } + IndexType = typeof(TextFieldIndex), + Index = nameof(TextFieldIndex.Text) } }, { @@ -99,11 +83,8 @@ public class ContentFieldsProvider : IContentFieldProvider FieldType = typeof(TimeSpanGraphType), UnderlyingType = typeof(TimeField), FieldAccessor = field => ((TimeField)field).Value, - IndexDescriptor = new FieldTypeIndexDescriptor - { - IndexType = typeof(TimeFieldIndex), - Index = nameof(TimeFieldIndex.Time) - } + IndexType = typeof(TimeFieldIndex), + Index = nameof(TimeFieldIndex.Time) } }, { @@ -150,20 +131,26 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech public bool HasField(ContentPartFieldDefinition field) => _contentFieldTypeMappings.ContainsKey(field.FieldDefinition.Name); - public (string, Type) GetFieldIndex(ContentPartFieldDefinition field) + public FieldTypeIndexDescriptor GetFieldIndex(ContentPartFieldDefinition field) { if (!HasFieldIndex(field)) { - return (null, null); + return null; } var fieldDescriptor = _contentFieldTypeMappings[field.FieldDefinition.Name]; - return (fieldDescriptor.IndexDescriptor.Index, fieldDescriptor.IndexDescriptor.IndexType); + + return new FieldTypeIndexDescriptor + { + Index = fieldDescriptor.Index, + IndexType = fieldDescriptor.IndexType + }; } public bool HasFieldIndex(ContentPartFieldDefinition field) => _contentFieldTypeMappings.TryGetValue(field.FieldDefinition.Name, out var fieldTypeDescriptor) && - fieldTypeDescriptor.IndexDescriptor != null; + fieldTypeDescriptor.IndexType != null && + !string.IsNullOrWhiteSpace(fieldTypeDescriptor.Index); private sealed class FieldTypeDescriptor { @@ -171,13 +158,8 @@ private sealed class FieldTypeDescriptor public Type FieldType { get; set; } public Type UnderlyingType { get; set; } public Func FieldAccessor { get; set; } - public FieldTypeIndexDescriptor IndexDescriptor { get; set; } - } - - private sealed class FieldTypeIndexDescriptor - { - public Type IndexType { get; set; } public string Index { get; set; } + public Type IndexType { get; set; } } } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs index adb169c5c3d..240796fc28e 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/GraphQL/Fields/ObjectGraphTypeFieldProvider.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -53,9 +52,9 @@ public FieldType GetField(ContentPartFieldDefinition field, string namedPartTech return null; } - public (string, Type) GetFieldIndex(ContentPartFieldDefinition field) + public FieldTypeIndexDescriptor GetFieldIndex(ContentPartFieldDefinition field) { - return (null, null); + return null; } private IObjectGraphType GetObjectGraphType(ContentPartFieldDefinition field) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 30f823f2f0e..6de0e237b3c 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -57,13 +57,13 @@ public IEnumerable GetAliases() continue; } - var (index, indexType) = fieldProvider.GetFieldIndex(field); + var fieldIndex = fieldProvider.GetFieldIndex(field); tenantAliases.Add(new IndexAlias { Alias = alias, - Index = index, - IndexType = indexType + Index = fieldIndex.Index, + IndexType = fieldIndex.IndexType }); break; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs index 72c8db12935..787c06e3839 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentFieldProvider.cs @@ -10,8 +10,14 @@ public interface IContentFieldProvider bool HasField(ContentPartFieldDefinition field); - (string, Type) GetFieldIndex(ContentPartFieldDefinition field); + FieldTypeIndexDescriptor GetFieldIndex(ContentPartFieldDefinition field); bool HasFieldIndex(ContentPartFieldDefinition field); } + + public sealed class FieldTypeIndexDescriptor + { + public required string Index { get; set; } + public required Type IndexType { get; set; } + } } From 2f13f45431ce8662609409cd8190517da9b3d85b Mon Sep 17 00:00:00 2001 From: Mohammad Dameer Date: Tue, 28 May 2024 16:22:16 +0300 Subject: [PATCH 08/29] Check if field index is null Co-authored-by: Tony Han --- .../Queries/Types/DynamicContentFieldsIndexAliasProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 6de0e237b3c..d20a6033d37 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -58,6 +58,10 @@ public IEnumerable GetAliases() } var fieldIndex = fieldProvider.GetFieldIndex(field); + if(fieldIndex is null) + { + continue; + } tenantAliases.Add(new IndexAlias { From 1cd80e02c03a1303bc38a7a11e72b55106d03893 Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 28 May 2024 16:23:23 +0300 Subject: [PATCH 09/29] DynamicContentFieldsIndexAliasProvider.cs formatting --- .../Queries/Types/DynamicContentFieldsIndexAliasProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index d20a6033d37..6c314f7b193 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -58,7 +58,8 @@ public IEnumerable GetAliases() } var fieldIndex = fieldProvider.GetFieldIndex(field); - if(fieldIndex is null) + + if (fieldIndex is null) { continue; } From cff031d6e363bbb2a64c4348ba1b100dac6ca33d Mon Sep 17 00:00:00 2001 From: mdameer Date: Fri, 31 May 2024 12:14:17 +0300 Subject: [PATCH 10/29] Fix merge conflicts --- .../Types/DynamicContentTypeBuilder.cs | 123 +++++++++--------- .../Types/DynamicContentTypeInputBuilder.cs | 8 +- .../Types/DynamicContentTypeQueryBuilder.cs | 8 +- .../Types/DynamicPartInputGraphType.cs | 39 ++++-- 4 files changed, 95 insertions(+), 83 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 53e2696b25c..7225de16560 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -2,6 +2,7 @@ using System.Linq; using GraphQL.Types; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using OrchardCore.Apis.GraphQL.Queries; @@ -13,18 +14,15 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types public abstract class DynamicContentTypeBuilder : IContentTypeBuilder { protected readonly IHttpContextAccessor _httpContextAccessor; - protected readonly IEnumerable _contentFieldProviders; protected readonly GraphQLContentOptions _contentOptions; protected readonly IStringLocalizer S; private readonly Dictionary _dynamicPartFields; public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, - IEnumerable contentFieldProviders, IOptions contentOptionsAccessor, IStringLocalizer localizer) { _httpContextAccessor = httpContextAccessor; - _contentFieldProviders = contentFieldProviders; _contentOptions = contentOptionsAccessor.Value; _dynamicPartFields = []; @@ -40,6 +38,9 @@ public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDef return; } + var serviceProvider = _httpContextAccessor.HttpContext.RequestServices; + var contentFieldProviders = serviceProvider.GetServices().ToList(); + foreach (var part in contentTypeDefinition.Parts) { var partName = part.Name; @@ -55,7 +56,7 @@ public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDef continue; } - if (!part.PartDefinition.Fields.Any(field => _contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(schema, field)))) + if (!part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(fieldProvider => fieldProvider.HasField(schema, field)))) { continue; } @@ -64,103 +65,107 @@ public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDef { foreach (var field in part.PartDefinition.Fields) { - foreach (var fieldProvider in _contentFieldProviders) + foreach (var fieldProvider in contentFieldProviders) { var customFieldName = GraphQLContentOptions.GetFieldName(part, part.Name, field.Name); - var fieldType = fieldProvider.GetField(schema, field, part.Name, customFieldName); + var contentFieldType = fieldProvider.GetField(schema, field, part.Name, customFieldName); - if (fieldType != null) + if (contentFieldType != null) { - if (_contentOptions.ShouldSkip(fieldType.Type, fieldType.Name)) + if (_contentOptions.ShouldSkip(contentFieldType.Type, contentFieldType.Name)) { continue; } - if (graphType is WhereInputObjectGraphType whereGraphType) + if (graphType is WhereInputObjectGraphType _whereGraphType) { if (fieldProvider.HasFieldIndex(field)) { - whereGraphType.AddScalarFilterFields(fieldType); + _whereGraphType.AddScalarFilterFields(contentFieldType); } } else { - graphType.AddField(fieldType); + graphType.AddField(contentFieldType); } break; } } } + + continue; } - else + + // Check if another builder has already added a field for this part. + var existingField = graphType.GetField(partName.ToFieldName()); + if (existingField != null) { - // Check if another builder has already added a field for this part. - var existingField = graphType.GetField(partName.ToFieldName()); - if (existingField != null) + // Add content field types. + foreach (var field in part.PartDefinition.Fields) { - // Add content field types. - foreach (var field in part.PartDefinition.Fields) + foreach (var fieldProvider in contentFieldProviders) { - foreach (var fieldProvider in _contentFieldProviders) - { - var contentFieldType = fieldProvider.GetField(schema, field, part.Name); + var contentFieldType = fieldProvider.GetField(schema, field, part.Name); - if (contentFieldType != null && !graphType.HasField(contentFieldType.Name)) + if (contentFieldType != null && !graphType.HasField(contentFieldType.Name)) + { + if (graphType is WhereInputObjectGraphType _whereGraphType) { - if (graphType is WhereInputObjectGraphType whereGraphType) - { - if (fieldProvider.HasFieldIndex(field)) - { - whereGraphType.AddScalarFilterFields(contentFieldType); - } - } - else + if (fieldProvider.HasFieldIndex(field)) { - graphType.AddField(contentFieldType); + _whereGraphType.AddScalarFilterFields(contentFieldType); } - - break; } + else + { + graphType.AddField(contentFieldType); + } + + break; } } } - else if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) + + continue; + } + + if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) + { + if (graphType is WhereInputObjectGraphType _whereGraphType) { - if (graphType is WhereInputObjectGraphType whereGraphType) - { - whereGraphType.AddScalarFilterFields(fieldType); - } - else - { - graphType.AddField(fieldType); - } + _whereGraphType.AddScalarFilterFields(fieldType); } else { - if (graphType is WhereInputObjectGraphType whereGraphType) - { - if (part.PartDefinition.Fields.Any(field => _contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) - { - var inputField = whereGraphType - .Field(partName.ToFieldName()) - .Description(S["Represents a {0} input.", part.PartDefinition.Name]); + graphType.AddField(fieldType); + } - inputField.Type(new DynamicPartInputGraphType(_httpContextAccessor, part)); - _dynamicPartFields[partName] = inputField.FieldType; - } - } - else - { - var field = graphType - .Field(partName.ToFieldName()) - .Description(S["Represents a {0}.", part.PartDefinition.Name]); + continue; + } + + if (graphType is WhereInputObjectGraphType whereGraphType) + { + if (part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) + { + var inputField = whereGraphType + .Field(partName.ToFieldName()) + .Description(S["Represents a {0} input.", part.PartDefinition.Name]); - field.Type(new DynamicPartGraphType(part)); - _dynamicPartFields[partName] = field.FieldType; + inputField.Type(new DynamicPartInputGraphType(part)); + _dynamicPartFields[partName] = inputField.FieldType; } } + else + { + var field = graphType + .Field(partName.ToFieldName()) + .Description(S["Represents a {0}.", part.PartDefinition.Name]); + + field.Type(new DynamicPartGraphType(part)); + _dynamicPartFields[partName] = field.FieldType; + } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs index b72c1cf8893..4de96537a36 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Linq; using GraphQL.Types; using Microsoft.AspNetCore.Http; @@ -12,18 +11,17 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types public class DynamicContentTypeInputBuilder : DynamicContentTypeBuilder { public DynamicContentTypeInputBuilder(IHttpContextAccessor httpContextAccessor, - IEnumerable contentFieldProviders, IOptions contentOptionsAccessor, IStringLocalizer localizer) - : base(httpContextAccessor, contentFieldProviders, contentOptionsAccessor, localizer) { } + : base(httpContextAccessor, contentOptionsAccessor, localizer) { } - public override void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; if (whereInputType != null) { - BuildInternal(contentQuery, contentTypeDefinition, whereInputType); + BuildInternal(schema, contentQuery, contentTypeDefinition, whereInputType); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs index 21d81cd3f9c..daf617fa097 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using GraphQL.Types; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Localization; @@ -11,14 +10,13 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types public class DynamicContentTypeQueryBuilder : DynamicContentTypeBuilder { public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, - IEnumerable contentFieldProviders, IOptions contentOptionsAccessor, IStringLocalizer localizer) - : base(httpContextAccessor, contentFieldProviders, contentOptionsAccessor, localizer) { } + : base(httpContextAccessor, contentOptionsAccessor, localizer) { } - public override void Build(FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { - BuildInternal(contentQuery, contentTypeDefinition, contentItemType); + BuildInternal(schema, contentQuery, contentTypeDefinition, contentItemType); } } } \ No newline at end of file diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs index 61b960c6fc7..4b8d4829b1e 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs @@ -1,5 +1,6 @@ +using System; using System.Linq; -using Microsoft.AspNetCore.Http; +using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; using OrchardCore.Apis.GraphQL.Queries; using OrchardCore.ContentManagement.Metadata.Models; @@ -8,27 +9,37 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; public sealed class DynamicPartInputGraphType : WhereInputObjectGraphType { - public DynamicPartInputGraphType(IHttpContextAccessor httpContextAccessor, ContentTypePartDefinition part) + private ContentTypePartDefinition _part; + + public DynamicPartInputGraphType(ContentTypePartDefinition part) { Name = $"{part.Name}WhereInput"; + _part = part; + } - var serviceProvider = httpContextAccessor.HttpContext.RequestServices; - var contentFieldProviders = serviceProvider.GetServices().ToList(); - - foreach (var field in part.PartDefinition.Fields) + public override void Initialize(ISchema schema) + { + if (schema is IServiceProvider serviceProvider) { - foreach (var fieldProvider in contentFieldProviders) - { - var fieldType = fieldProvider.GetField(field, part.Name); + var contentFieldProviders = serviceProvider.GetServices().ToList(); - if (fieldType == null) + foreach (var field in _part.PartDefinition.Fields) + { + foreach (var fieldProvider in contentFieldProviders) { - continue; + var fieldType = fieldProvider.GetField(schema, field, _part.Name); + if (fieldType != null) + { + AddScalarFilterFields(fieldType); + break; + } } - - AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); - break; } } + + // Part is not required here anymore, do not keep it alive. + _part = null; + + base.Initialize(schema); } } From 926b12afac7b31c952b57cbac0078ab1a578792e Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 4 Jun 2024 03:04:11 +0300 Subject: [PATCH 11/29] Differentiate query and input dynamic builders, dynamic content type query tests --- .../Queries/ContentItemsFieldType.cs | 2 +- .../Queries/Types/ContentItemType.cs | 7 +- .../Queries/Types/ContentItemWhereInput.cs | 17 +- .../Types/DynamicContentTypeBuilder.cs | 75 ++++--- .../Types/DynamicContentTypeInputBuilder.cs | 28 --- .../Types/DynamicContentTypeQueryBuilder.cs | 21 +- .../DynamicContentTypeWhereInputBuilder.cs | 27 +++ ...e.cs => DynamicPartWhereInputGraphType.cs} | 4 +- .../Types/IContentItemObjectGraphType.cs | 13 ++ .../ServiceCollectionExtensions.cs | 3 +- .../Apis/Context/TestRecipeHarvester.cs | 1 + .../DynamicContentTypeContext.cs | 32 +++ .../DynamicContentTypeQueryTests.cs | 49 +++++ .../Recipes/DynamicContentTypeQueryTest.json | 184 ++++++++++++++++++ .../OrchardCore.Tests.csproj | 2 + 15 files changed, 391 insertions(+), 74 deletions(-) delete mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs rename src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/{DynamicPartInputGraphType.cs => DynamicPartWhereInputGraphType.cs} (87%) create mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs create mode 100644 test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs create mode 100644 test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs create mode 100644 test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index bbfab670a56..7b3b47b6522 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -283,7 +283,7 @@ private void BuildExpressionsInternal(JsonObject where, Junction expressions, st if (whereArgument != null) { - var whereInput = (WhereInputObjectGraphType)whereArgument.ResolvedType; + var whereInput = (WhereInputObjectGraphType)whereArgument.ResolvedType; foreach (var field in whereInput.Fields.Where(x => x.GetMetadata("PartName") != null)) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs index c16e5e39b20..8f2b69ab696 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs @@ -13,7 +13,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { - public class ContentItemType : ObjectGraphType + public class ContentItemType : ObjectGraphType, IContentItemObjectGraphType { private readonly GraphQLContentOptions _options; @@ -58,6 +58,11 @@ public override FieldType AddField(FieldType fieldType) return null; } + public void AddOutputField(FieldType fieldType) + { + AddField(fieldType); + } + private static async ValueTask RenderShapeAsync(IResolveFieldContext context) { var serviceProvider = context.RequestServices; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs index 0979467ae08..4e7b5998bf2 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs @@ -1,3 +1,4 @@ +using System; using GraphQL.Types; using Microsoft.Extensions.Options; using OrchardCore.Apis.GraphQL; @@ -9,7 +10,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types [GraphQLFieldName("Or", "OR")] [GraphQLFieldName("And", "AND")] [GraphQLFieldName("Not", "NOT")] - public class ContentItemWhereInput : WhereInputObjectGraphType + public class ContentItemWhereInput : WhereInputObjectGraphType, IContentItemInputObjectGraphType { private readonly IOptions _optionsAccessor; @@ -36,11 +37,21 @@ public ContentItemWhereInput(string contentItemName, IOptions>("Not").Description("NOT logical operation").Type(whereInputType); } - private void AddFilterField(string name, string description) + public void AddInputField(FieldType fieldType) + { + AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); + } + + private void AddFilterField(string name, string description) + { + AddFilterField(typeof(TGraphType), name, description); + } + + private void AddFilterField(Type graphType, string name, string description) { if (!_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), name)) { - AddScalarFilterFields(name, description); + AddScalarFilterFields(graphType, name, description); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 7225de16560..afd7c18294c 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; using System.Linq; +using GraphQL.Resolvers; using GraphQL.Types; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; -using OrchardCore.Apis.GraphQL.Queries; using OrchardCore.ContentManagement.GraphQL.Options; using OrchardCore.ContentManagement.Metadata.Models; @@ -31,7 +31,17 @@ public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, public abstract void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); - public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ComplexGraphType graphType) + protected void BuildObjectGraphType(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, IContentItemObjectGraphType objectGraphType) + { + BuildInternal(schema, contentQuery, contentTypeDefinition, objectGraphType); + } + + protected void BuildInputObjectGraphType(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, IContentItemInputObjectGraphType inputObjectGraphType) + { + BuildInternal(schema, contentQuery, contentTypeDefinition, inputObjectGraphType); + } + + private void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, IComplexGraphType graphType) { if (_contentOptions.ShouldHide(contentTypeDefinition)) { @@ -78,16 +88,16 @@ public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDef continue; } - if (graphType is WhereInputObjectGraphType _whereGraphType) + if (graphType is IContentItemInputObjectGraphType _inputGraphType) { if (fieldProvider.HasFieldIndex(field)) { - _whereGraphType.AddScalarFilterFields(contentFieldType); + _inputGraphType.AddInputField(contentFieldType); } } - else + else if (graphType is IContentItemObjectGraphType _graphType) { - graphType.AddField(contentFieldType); + _graphType.AddOutputField(contentFieldType); } break; @@ -111,16 +121,16 @@ public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDef if (contentFieldType != null && !graphType.HasField(contentFieldType.Name)) { - if (graphType is WhereInputObjectGraphType _whereGraphType) + if (graphType is IContentItemInputObjectGraphType _inputGraphType) { if (fieldProvider.HasFieldIndex(field)) { - _whereGraphType.AddScalarFilterFields(contentFieldType); + _inputGraphType.AddInputField(contentFieldType); } } - else + else if (graphType is IContentItemObjectGraphType _graphType) { - graphType.AddField(contentFieldType); + _graphType.AddOutputField(contentFieldType); } break; @@ -133,38 +143,51 @@ public void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDef if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) { - if (graphType is WhereInputObjectGraphType _whereGraphType) + if (graphType is IContentItemInputObjectGraphType _inputGraphType) { - _whereGraphType.AddScalarFilterFields(fieldType); + _inputGraphType.AddInputField(fieldType); } - else + else if (graphType is IContentItemObjectGraphType _graphType) { - graphType.AddField(fieldType); + _graphType.AddOutputField(fieldType); } continue; } - if (graphType is WhereInputObjectGraphType whereGraphType) + if (graphType is IContentItemInputObjectGraphType inputGraphType) { if (part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) { - var inputField = whereGraphType - .Field(partName.ToFieldName()) - .Description(S["Represents a {0} input.", part.PartDefinition.Name]); + var field = new FieldType + { + Name = partName.ToFieldName(), + Description = S["Represents a {0}.", part.PartDefinition.Name], + ResolvedType = new DynamicPartWhereInputGraphType(part) + }; - inputField.Type(new DynamicPartInputGraphType(part)); - _dynamicPartFields[partName] = inputField.FieldType; + inputGraphType.AddField(field); + _dynamicPartFields[partName] = field; } } - else + else if (graphType is IContentItemObjectGraphType _graphType) { - var field = graphType - .Field(partName.ToFieldName()) - .Description(S["Represents a {0}.", part.PartDefinition.Name]); + var field = new FieldType + { + Name = partName.ToFieldName(), + Description = S["Represents a {0}.", part.PartDefinition.Name], + ResolvedType = new DynamicPartGraphType(part), + Resolver = new FuncFieldResolver(context => + { + var nameToResolve = partName; + var typeToResolve = context.FieldDefinition.ResolvedType.GetType().BaseType.GetGenericArguments().First(); + + return context.Source.Get(typeToResolve, nameToResolve); + }) + }; - field.Type(new DynamicPartGraphType(part)); - _dynamicPartFields[partName] = field.FieldType; + _graphType.AddField(field); + _dynamicPartFields[partName] = field; } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs deleted file mode 100644 index 4de96537a36..00000000000 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeInputBuilder.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using GraphQL.Types; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using OrchardCore.ContentManagement.GraphQL.Options; -using OrchardCore.ContentManagement.Metadata.Models; - -namespace OrchardCore.ContentManagement.GraphQL.Queries.Types -{ - public class DynamicContentTypeInputBuilder : DynamicContentTypeBuilder - { - public DynamicContentTypeInputBuilder(IHttpContextAccessor httpContextAccessor, - IOptions contentOptionsAccessor, - IStringLocalizer localizer) - : base(httpContextAccessor, contentOptionsAccessor, localizer) { } - - public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) - { - var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; - - if (whereInputType != null) - { - BuildInternal(schema, contentQuery, contentTypeDefinition, whereInputType); - } - } - } -} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs index daf617fa097..e61379a67c5 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -5,18 +5,17 @@ using OrchardCore.ContentManagement.GraphQL.Options; using OrchardCore.ContentManagement.Metadata.Models; -namespace OrchardCore.ContentManagement.GraphQL.Queries.Types +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; + +public class DynamicContentTypeQueryBuilder : DynamicContentTypeBuilder { - public class DynamicContentTypeQueryBuilder : DynamicContentTypeBuilder - { - public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, - IOptions contentOptionsAccessor, - IStringLocalizer localizer) - : base(httpContextAccessor, contentOptionsAccessor, localizer) { } + public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, + IOptions contentOptionsAccessor, + IStringLocalizer localizer) + : base(httpContextAccessor, contentOptionsAccessor, localizer) { } - public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) - { - BuildInternal(schema, contentQuery, contentTypeDefinition, contentItemType); - } + public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + { + BuildObjectGraphType(schema, contentQuery, contentTypeDefinition, contentItemType); } } \ No newline at end of file diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs new file mode 100644 index 00000000000..ce3a5008cf9 --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs @@ -0,0 +1,27 @@ +using System.Linq; +using GraphQL.Types; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using OrchardCore.ContentManagement.GraphQL.Options; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; + +public class DynamicContentTypeWhereInputBuilder : DynamicContentTypeBuilder +{ + public DynamicContentTypeWhereInputBuilder(IHttpContextAccessor httpContextAccessor, + IOptions contentOptionsAccessor, + IStringLocalizer localizer) + : base(httpContextAccessor, contentOptionsAccessor, localizer) { } + + public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) + { + var whereInputType = (ContentItemWhereInput)contentQuery.Arguments?.FirstOrDefault(x => x.Name == "where")?.ResolvedType; + + if (whereInputType != null) + { + BuildInputObjectGraphType(schema, contentQuery, contentTypeDefinition, whereInputType); + } + } +} diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs similarity index 87% rename from src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs rename to src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs index 4b8d4829b1e..b91cd75584b 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartInputGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs @@ -7,11 +7,11 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; -public sealed class DynamicPartInputGraphType : WhereInputObjectGraphType +public sealed class DynamicPartWhereInputGraphType : WhereInputObjectGraphType { private ContentTypePartDefinition _part; - public DynamicPartInputGraphType(ContentTypePartDefinition part) + public DynamicPartWhereInputGraphType(ContentTypePartDefinition part) { Name = $"{part.Name}WhereInput"; _part = part; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs new file mode 100644 index 00000000000..60ea423d0a4 --- /dev/null +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs @@ -0,0 +1,13 @@ +using GraphQL.Types; + +namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; + +public interface IContentItemObjectGraphType : IObjectGraphType +{ + void AddOutputField(FieldType fieldType); +} + +public interface IContentItemInputObjectGraphType : IInputObjectGraphType +{ + void AddInputField(FieldType fieldType); +} \ No newline at end of file diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs index f72087cb66a..a5d0cbbdbab 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/ServiceCollectionExtensions.cs @@ -35,9 +35,8 @@ public static IServiceCollection AddContentGraphQL(this IServiceCollection servi public static IServiceCollection AddContentFieldsInputGraphQL(this IServiceCollection services) { - services.AddTransient(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); return services; diff --git a/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs b/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs index e381777ac41..9f7803f376f 100644 --- a/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs +++ b/test/OrchardCore.Tests/Apis/Context/TestRecipeHarvester.cs @@ -16,6 +16,7 @@ public Task> HarvestRecipesAsync() => HarvestRecipesAsync( [ "Apis/Lucene/Recipes/luceneQueryTest.json", + "Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json", "OrchardCore.Users/Recipes/UserSettingsTest.json" ]); diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs new file mode 100644 index 00000000000..5207d52b46d --- /dev/null +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs @@ -0,0 +1,32 @@ +using OrchardCore.Tests.Apis.Context; + +namespace OrchardCore.Tests.Apis.GraphQL; + +public class DynamicContentTypeContext : SiteContext +{ + public string Product1ContentItemId { get; private set; } + public string Product2ContentItemId { get; private set; } + + public DynamicContentTypeContext() + { + this.WithRecipe("DynamicContentTypeBuilderTest"); + } + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + + var products = await GraphQLClient + .Content + .Query("product", builder => + { + builder + .WithQueryArgument("orderBy", "createdUtc", "ASC") + .WithField("contentItemId") + .WithField("displayText"); + }); + + Product1ContentItemId = products["data"]["product"][0]["contentItemId"].ToString(); + Product2ContentItemId = products["data"]["product"][1]["contentItemId"].ToString(); + } +} diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs new file mode 100644 index 00000000000..22c70705675 --- /dev/null +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs @@ -0,0 +1,49 @@ +namespace OrchardCore.Tests.Apis.GraphQL; + +public class DynamicContentTypeQueryTests +{ + [Fact] + public async Task ShouldQueryContentFields() + { + using var context = new DynamicContentTypeContext(); + await context.InitializeAsync(); + + var result = await context + .GraphQLClient + .Content + .Query("product(where: {price: {price_gt: 10}}) {contentItemId, displayText}"); + + Assert.Single( + result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product2ContentItemId)); + } + + [Fact] + public async Task ShouldQueryCollapsedContentFields() + { + using var context = new DynamicContentTypeContext(); + await context.InitializeAsync(); + + var result = await context + .GraphQLClient + .Content + .Query("product(where: {sku_starts_with: \"4000\"}) {contentItemId, displayText}"); + + Assert.Single( + result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product2ContentItemId)); + } + + [Fact] + public async Task ShouldQueryCollapsedContentFieldsWithPreventCollision() + { + using var context = new DynamicContentTypeContext(); + await context.InitializeAsync(); + + var result = await context + .GraphQLClient + .Content + .Query("product(where: {productCodeCode: \"100000987\"}) {contentItemId, displayText}"); + + Assert.Single( + result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product1ContentItemId)); + } +} \ No newline at end of file diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json new file mode 100644 index 00000000000..f5518dca5ed --- /dev/null +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json @@ -0,0 +1,184 @@ +{ + "name": "DynamicContentTypeQueryTest", + "displayName": "Dynamic Content Type Query Test", + "description": "", + "website": "", + "issetuprecipe": true, + "categories": [ + "test" + ], + "tags": [], + "variables": {}, + "steps": [ + { + "name": "feature", + "disable": [], + "enable": [ + "OrchardCore.Admin", + "OrchardCore.Recipes", + "OrchardCore.Roles", + "OrchardCore.Users", + "OrchardCore.Indexing", + "OrchardCore.ContentFields", + "OrchardCore.Apis.GraphQL", + "OrchardCore.ContentFields.Indexing.SQL", + "OrchardCore.ContentTypes", + "OrchardCore.Contents", + "OrchardCore.Contents.FileContentDefinition" + ] + }, + { + "name": "ContentDefinition", + "ContentTypes": [ + { + "Name": "Product", + "DisplayName": "Product", + "Settings": { + "ContentTypeSettings": { + "Creatable": true, + "Listable": true + } + }, + "ContentTypePartDefinitionRecords": [ + { + "PartName": "PricePart", + "Name": "PricePart", + "Settings": { + "ContentTypePartSettings": { + "Position": "1" + } + } + }, + { + "PartName": "SkuPart", + "Name": "SkuPart", + "Settings": { + "ContentTypePartSettings": { + "Position": "2" + }, + "GraphQLContentTypePartSettings": { + "Collapse": true, + "Hidden": false, + "PreventFieldNameCollision": false + } + } + }, + { + "PartName": "ProductCodePart", + "Name": "ProductCodePart", + "Settings": { + "ContentTypePartSettings": { + "Position": "2" + }, + "GraphQLContentTypePartSettings": { + "Collapse": true, + "Hidden": false, + "PreventFieldNameCollision": true + } + } + } + ] + } + ], + "ContentParts": [ + { + "Name": "PricePart", + "Settings": {}, + "ContentPartFieldDefinitionRecords": [ + { + "FieldName": "NumericField", + "Name": "Price", + "Settings": { + "ContentPartFieldSettings": { + "DisplayName": "Price", + "Position": "0" + } + } + } + ] + }, + { + "Name": "SkuPart", + "Settings": {}, + "ContentPartFieldDefinitionRecords": [ + { + "FieldName": "TextField", + "Name": "Sku", + "Settings": { + "ContentPartFieldSettings": { + "DisplayName": "Sku", + "Position": "0" + } + } + } + ] + }, + { + "Name": "ProductCodePart", + "Settings": {}, + "ContentPartFieldDefinitionRecords": [ + { + "FieldName": "TextField", + "Name": "Code", + "Settings": { + "ContentPartFieldSettings": { + "DisplayName": "Code", + "Position": "0" + } + } + } + ] + } + ] + }, + { + "name": "content", + "data": [ + { + "ContentItemId": "[js:uuid()]", + "ContentType": "Product", + "DisplayText": "Product 1", + "Latest": true, + "Published": true, + "PricePart": { + "Price": { + "Value": 10 + } + }, + "SkuPart": { + "Sku": { + "Text": "3000-01" + } + }, + "ProductCodePart": { + "Code": { + "Text": "100000987" + } + } + }, + { + "ContentItemId": "[js:uuid()]", + "ContentType": "Product", + "DisplayText": "Product 2", + "Latest": true, + "Published": true, + "PricePart": { + "Price": { + "Value": 22 + } + }, + "SkuPart": { + "Sku": { + "Text": "4000-44" + } + }, + "ProductCodePart": { + "Code": { + "Text": "100000988" + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj index a11ad9ecb36..d77d149e222 100644 --- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj +++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj @@ -21,6 +21,7 @@ + @@ -37,6 +38,7 @@ + From 95fb3bf068fb6b3205696f4c7624ac6b578af8ad Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 4 Jun 2024 03:13:20 +0300 Subject: [PATCH 12/29] Remove multi value filters for boolean graph type --- .../Queries/WhereInputObjectGraphType.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs index 45c53844807..66826332d25 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs @@ -71,10 +71,10 @@ public void AddScalarFilterFields(Type graphType, string fieldName, string descr } AddEqualityFilters(graphType, fieldName, description); - AddMultiValueFilters(graphType, fieldName, description); if (graphType == typeof(StringGraphType)) { + AddMultiValueFilters(graphType, fieldName, description); AddStringFilters(graphType, fieldName, description); } else if (graphType == typeof(DateTimeGraphType) || @@ -87,6 +87,7 @@ public void AddScalarFilterFields(Type graphType, string fieldName, string descr graphType == typeof(FloatGraphType) || graphType == typeof(BigIntGraphType)) { + AddMultiValueFilters(graphType, fieldName, description); AddNonStringFilters(graphType, fieldName, description); } } From 381e6b6013919224efd410b6dbed4a78b6d187f1 Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 4 Jun 2024 21:27:43 +0300 Subject: [PATCH 13/29] Fix dynamic content type test recipe name --- .../DynamicContentTypeContext.cs | 2 +- .../DynamicContentTypeQueryTests.cs | 30 +++++++++++++++++-- .../OrchardCore.Tests.csproj | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs index 5207d52b46d..91d2bb99533 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeContext.cs @@ -9,7 +9,7 @@ public class DynamicContentTypeContext : SiteContext public DynamicContentTypeContext() { - this.WithRecipe("DynamicContentTypeBuilderTest"); + this.WithRecipe("DynamicContentTypeQueryTest"); } public override async Task InitializeAsync() diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs index 22c70705675..a6d4017f139 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs @@ -11,7 +11,15 @@ public async Task ShouldQueryContentFields() var result = await context .GraphQLClient .Content - .Query("product(where: {price: {price_gt: 10}}) {contentItemId, displayText}"); + .Query(@"product(where: {price: {price_gt: 10}}) { + contentItemId + displayText + price { + price + } + sku + productCodeCode + }"); Assert.Single( result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product2ContentItemId)); @@ -26,7 +34,15 @@ public async Task ShouldQueryCollapsedContentFields() var result = await context .GraphQLClient .Content - .Query("product(where: {sku_starts_with: \"4000\"}) {contentItemId, displayText}"); + .Query(@"product(where: {sku_starts_with: ""4000""}) { + contentItemId + displayText + price { + price + } + sku + productCodeCode + }"); Assert.Single( result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product2ContentItemId)); @@ -41,7 +57,15 @@ public async Task ShouldQueryCollapsedContentFieldsWithPreventCollision() var result = await context .GraphQLClient .Content - .Query("product(where: {productCodeCode: \"100000987\"}) {contentItemId, displayText}"); + .Query(@"product(where: {productCodeCode: ""100000987""}) { + contentItemId + displayText + price { + price + } + sku + productCodeCode + }"); Assert.Single( result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product1ContentItemId)); diff --git a/test/OrchardCore.Tests/OrchardCore.Tests.csproj b/test/OrchardCore.Tests/OrchardCore.Tests.csproj index d77d149e222..8b10da38e3c 100644 --- a/test/OrchardCore.Tests/OrchardCore.Tests.csproj +++ b/test/OrchardCore.Tests/OrchardCore.Tests.csproj @@ -21,7 +21,7 @@ - + From 68093fb98972f75ce22ce282a8ab7eaf45308fa0 Mon Sep 17 00:00:00 2001 From: mdameer Date: Tue, 4 Jun 2024 22:16:43 +0300 Subject: [PATCH 14/29] Add query multiple content fields test --- .../DynamicContentTypeQueryTests.cs | 59 +++++++++++++++---- .../Recipes/DynamicContentTypeQueryTest.json | 26 ++++++-- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs index a6d4017f139..83e6a6c7d55 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs @@ -18,11 +18,13 @@ public async Task ShouldQueryContentFields() price } sku - productCodeCode + metadataCode + metadataAvailabilityDate }"); - Assert.Single( - result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product2ContentItemId)); + Assert.Single(result["data"]["product"].AsArray()); + + Assert.Equal(context.Product2ContentItemId, result["data"]["product"].AsArray().First()["contentItemId"].ToString()); } [Fact] @@ -41,11 +43,13 @@ public async Task ShouldQueryCollapsedContentFields() price } sku - productCodeCode + metadataCode + metadataAvailabilityDate }"); - Assert.Single( - result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product2ContentItemId)); + Assert.Single(result["data"]["product"].AsArray()); + + Assert.Equal(context.Product2ContentItemId, result["data"]["product"].AsArray().First()["contentItemId"].ToString()); } [Fact] @@ -57,17 +61,52 @@ public async Task ShouldQueryCollapsedContentFieldsWithPreventCollision() var result = await context .GraphQLClient .Content - .Query(@"product(where: {productCodeCode: ""100000987""}) { + .Query(@"product(where: {metadataCode: ""100000987""}) { + contentItemId + displayText + price { + price + } + sku + metadataCode + metadataAvailabilityDate + }"); + + Assert.Single(result["data"]["product"].AsArray()); + + Assert.Equal(context.Product1ContentItemId, result["data"]["product"].AsArray().First()["contentItemId"].ToString()); + } + + [Fact] + public async Task ShouldQueryMultipleContentFields() + { + using var context = new DynamicContentTypeContext(); + await context.InitializeAsync(); + + var result = await context + .GraphQLClient + .Content + .Query(@"product( + where: { + AND: { + OR: {price: {price: 10}, sku_ends_with: ""44""}, + metadataAvailabilityDate_gt: ""2024-05-01"" + } + }, + orderBy: {createdUtc: ASC} + ) { contentItemId displayText price { price } sku - productCodeCode + metadataCode + metadataAvailabilityDate }"); - Assert.Single( - result["data"]["product"].AsArray().Where(node => node["contentItemId"].ToString() == context.Product1ContentItemId)); + Assert.Single(result["data"]["product"].AsArray()); + + Assert.Equal(context.Product2ContentItemId, result["data"]["product"].AsArray().First()["contentItemId"].ToString()); } } \ No newline at end of file diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json index f5518dca5ed..45201f82f76 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json @@ -64,8 +64,8 @@ } }, { - "PartName": "ProductCodePart", - "Name": "ProductCodePart", + "PartName": "MetadataPart", + "Name": "MetadataPart", "Settings": { "ContentTypePartSettings": { "Position": "2" @@ -114,7 +114,7 @@ ] }, { - "Name": "ProductCodePart", + "Name": "MetadataPart", "Settings": {}, "ContentPartFieldDefinitionRecords": [ { @@ -126,6 +126,16 @@ "Position": "0" } } + }, + { + "FieldName": "DateField", + "Name": "AvailabilityDate", + "Settings": { + "ContentPartFieldSettings": { + "DisplayName": "Availability Date", + "Position": "0" + } + } } ] } @@ -150,9 +160,12 @@ "Text": "3000-01" } }, - "ProductCodePart": { + "MetadataPart": { "Code": { "Text": "100000987" + }, + "AvailabilityDate": { + "Value": "2024-04-07" } } }, @@ -172,9 +185,12 @@ "Text": "4000-44" } }, - "ProductCodePart": { + "MetadataPart": { "Code": { "Text": "100000988" + }, + "AvailabilityDate": { + "Value": "2024-05-18" } } } From 473666a0dcac4a319e4ca646f719c1b15a837824 Mon Sep 17 00:00:00 2001 From: mdameer Date: Thu, 6 Jun 2024 00:28:32 +0300 Subject: [PATCH 15/29] Add documentation for dynamic content fields usage in GraphQL --- .../reference/modules/SQLIndexing/README.md | 38 +++++++++++++++++++ .../DynamicContentTypeQueryTests.cs | 12 +++--- .../Recipes/DynamicContentTypeQueryTest.json | 8 ++-- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/docs/reference/modules/SQLIndexing/README.md b/src/docs/reference/modules/SQLIndexing/README.md index ecf745b7870..e5888a24df4 100644 --- a/src/docs/reference/modules/SQLIndexing/README.md +++ b/src/docs/reference/modules/SQLIndexing/README.md @@ -266,6 +266,44 @@ In our Liquid template we will now retrieve these records. Please note that Datetimes are stored as UTC so a conversion with the current request culture will be required. +## GraphQL Usage + +Enabling the `OrchardCore.ContentFields.Indexing.SQL` module allows building filters based on dynamic content fields in GraphQL queries. + +The following examples demonstrate filtering products based on a numeric content field named `Amount` attached to a content part named `PricePart` where the Amount is greater than 10: + +Normal usage: + +``` +product(where: {price: {amount_gt: 10}}) { + contentItemId + displayText + price { + amount + } +} +``` + +If the `PricePart` is collapsed: + +``` +product(where: {amount_gt: 10}) { + contentItemId + displayText + amount +} +``` + +If the `PricePart` is collaped with prevent field name collision option enabled: + +``` +product(where: {priceAmount_gt: 10}) { + contentItemId + displayText + priceAmount +} +``` + ## Video diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs index 83e6a6c7d55..29196ea324f 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs @@ -11,11 +11,11 @@ public async Task ShouldQueryContentFields() var result = await context .GraphQLClient .Content - .Query(@"product(where: {price: {price_gt: 10}}) { + .Query(@"product(where: {price: {amount_gt: 10}}) { contentItemId displayText price { - price + amount } sku metadataCode @@ -40,7 +40,7 @@ public async Task ShouldQueryCollapsedContentFields() contentItemId displayText price { - price + amount } sku metadataCode @@ -65,7 +65,7 @@ public async Task ShouldQueryCollapsedContentFieldsWithPreventCollision() contentItemId displayText price { - price + amount } sku metadataCode @@ -89,7 +89,7 @@ public async Task ShouldQueryMultipleContentFields() .Query(@"product( where: { AND: { - OR: {price: {price: 10}, sku_ends_with: ""44""}, + OR: {price: {amount: 10}, sku_ends_with: ""44""}, metadataAvailabilityDate_gt: ""2024-05-01"" } }, @@ -98,7 +98,7 @@ public async Task ShouldQueryMultipleContentFields() contentItemId displayText price { - price + amount } sku metadataCode diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json index 45201f82f76..915edbb4cf9 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json @@ -87,10 +87,10 @@ "ContentPartFieldDefinitionRecords": [ { "FieldName": "NumericField", - "Name": "Price", + "Name": "Amount", "Settings": { "ContentPartFieldSettings": { - "DisplayName": "Price", + "DisplayName": "Amount", "Position": "0" } } @@ -151,7 +151,7 @@ "Latest": true, "Published": true, "PricePart": { - "Price": { + "Amount": { "Value": 10 } }, @@ -176,7 +176,7 @@ "Latest": true, "Published": true, "PricePart": { - "Price": { + "Amount": { "Value": 22 } }, From 85a3dea4d743ba5eacd73ee508a1631176f199a1 Mon Sep 17 00:00:00 2001 From: Mohammad Dameer Date: Thu, 6 Jun 2024 18:36:27 +0300 Subject: [PATCH 16/29] Add a blank line at the end of the file Co-authored-by: Tony Han --- .../Queries/Types/ContentItemWhereInput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs index 4e7b5998bf2..0a524eabc4a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs @@ -55,4 +55,4 @@ private void AddFilterField(Type graphType, string name, string description) } } } -} \ No newline at end of file +} From 7cd08044d41a2f5e460d8ca1792380a27693697d Mon Sep 17 00:00:00 2001 From: Mohammad Dameer Date: Thu, 6 Jun 2024 18:37:26 +0300 Subject: [PATCH 17/29] Add a blank line at the end of the file Co-authored-by: Tony Han --- .../Queries/Types/IContentItemObjectGraphType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs index 60ea423d0a4..fc576ae0f0e 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs @@ -10,4 +10,4 @@ public interface IContentItemObjectGraphType : IObjectGraphType public interface IContentItemInputObjectGraphType : IInputObjectGraphType { void AddInputField(FieldType fieldType); -} \ No newline at end of file +} From 1f07cb32450b1e9ff310149eb283d2404fc5662a Mon Sep 17 00:00:00 2001 From: Mohammad Dameer Date: Thu, 6 Jun 2024 18:38:24 +0300 Subject: [PATCH 18/29] Use var instead of string Co-authored-by: Tony Han --- .../Queries/Types/DynamicContentFieldsIndexAliasProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 6c314f7b193..08f67c06458 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -46,7 +46,7 @@ public IEnumerable GetAliases() { foreach (var field in part.PartDefinition.Fields) { - string alias = _contentOptions.ShouldCollapse(part) ? + var alias = _contentOptions.ShouldCollapse(part) ? GraphQLContentOptions.GetFieldName(part, part.Name, field.Name) : $"{field.PartDefinition.Name.ToFieldName()}.{field.Name.ToCamelCase()}"; From a46bb7534b2a3b829b4e34568c32abb49648a645 Mon Sep 17 00:00:00 2001 From: Mohammad Dameer Date: Thu, 6 Jun 2024 18:39:00 +0300 Subject: [PATCH 19/29] Update src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs Co-authored-by: Tony Han --- .../Queries/Types/DynamicContentTypeQueryBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs index e61379a67c5..e76ed65e784 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -18,4 +18,4 @@ public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDe { BuildObjectGraphType(schema, contentQuery, contentTypeDefinition, contentItemType); } -} \ No newline at end of file +} From a90e4e580ecd719c859fbc366ae2ca017baf8e8f Mon Sep 17 00:00:00 2001 From: mdameer Date: Thu, 6 Jun 2024 18:44:40 +0300 Subject: [PATCH 20/29] Remove unused content query parameter in "DynamicContentTypeBuilder", Add end line for files --- .../Queries/Types/DynamicContentTypeBuilder.cs | 10 +++++----- .../Queries/Types/DynamicContentTypeQueryBuilder.cs | 2 +- .../Types/DynamicContentTypeWhereInputBuilder.cs | 2 +- .../ContentManagement/DynamicContentTypeQueryTests.cs | 2 +- .../Recipes/DynamicContentTypeQueryTest.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index afd7c18294c..ec77eb8c1c5 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -31,17 +31,17 @@ public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, public abstract void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); - protected void BuildObjectGraphType(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, IContentItemObjectGraphType objectGraphType) + protected void BuildObjectGraphType(ISchema schema, ContentTypeDefinition contentTypeDefinition, IContentItemObjectGraphType objectGraphType) { - BuildInternal(schema, contentQuery, contentTypeDefinition, objectGraphType); + BuildInternal(schema, contentTypeDefinition, objectGraphType); } - protected void BuildInputObjectGraphType(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, IContentItemInputObjectGraphType inputObjectGraphType) + protected void BuildInputObjectGraphType(ISchema schema, ContentTypeDefinition contentTypeDefinition, IContentItemInputObjectGraphType inputObjectGraphType) { - BuildInternal(schema, contentQuery, contentTypeDefinition, inputObjectGraphType); + BuildInternal(schema, contentTypeDefinition, inputObjectGraphType); } - private void BuildInternal(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, IComplexGraphType graphType) + private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefinition, IComplexGraphType graphType) { if (_contentOptions.ShouldHide(contentTypeDefinition)) { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs index e76ed65e784..599f2b7f5fb 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -16,6 +16,6 @@ public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { - BuildObjectGraphType(schema, contentQuery, contentTypeDefinition, contentItemType); + BuildObjectGraphType(schema, contentTypeDefinition, contentItemType); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs index ce3a5008cf9..ac85815ecce 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs @@ -21,7 +21,7 @@ public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDe if (whereInputType != null) { - BuildInputObjectGraphType(schema, contentQuery, contentTypeDefinition, whereInputType); + BuildInputObjectGraphType(schema, contentTypeDefinition, whereInputType); } } } diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs index 29196ea324f..130d1eacbca 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/DynamicContentTypeQueryTests.cs @@ -109,4 +109,4 @@ public async Task ShouldQueryMultipleContentFields() Assert.Equal(context.Product2ContentItemId, result["data"]["product"].AsArray().First()["contentItemId"].ToString()); } -} \ No newline at end of file +} diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json index 915edbb4cf9..28c2563f187 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/Recipes/DynamicContentTypeQueryTest.json @@ -197,4 +197,4 @@ ] } ] -} \ No newline at end of file +} From 3c53c26d4eec83071a9c58055b6d9b4eede1a313 Mon Sep 17 00:00:00 2001 From: mdameer Date: Thu, 6 Jun 2024 23:49:32 +0300 Subject: [PATCH 21/29] Add dynamic content field query release note for 2.0.0 --- src/docs/reference/modules/SQLIndexing/README.md | 6 +++--- src/docs/releases/2.0.0.md | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/docs/reference/modules/SQLIndexing/README.md b/src/docs/reference/modules/SQLIndexing/README.md index e5888a24df4..dbf021d9fcd 100644 --- a/src/docs/reference/modules/SQLIndexing/README.md +++ b/src/docs/reference/modules/SQLIndexing/README.md @@ -274,7 +274,7 @@ The following examples demonstrate filtering products based on a numeric content Normal usage: -``` +```graphql product(where: {price: {amount_gt: 10}}) { contentItemId displayText @@ -286,7 +286,7 @@ product(where: {price: {amount_gt: 10}}) { If the `PricePart` is collapsed: -``` +```graphql product(where: {amount_gt: 10}) { contentItemId displayText @@ -296,7 +296,7 @@ product(where: {amount_gt: 10}) { If the `PricePart` is collaped with prevent field name collision option enabled: -``` +```graphql product(where: {priceAmount_gt: 10}) { contentItemId displayText diff --git a/src/docs/releases/2.0.0.md b/src/docs/releases/2.0.0.md index 2894c957ca5..19748b2fbfc 100644 --- a/src/docs/releases/2.0.0.md +++ b/src/docs/releases/2.0.0.md @@ -301,6 +301,20 @@ services.Configure(options => }); ``` +The GraphQL module now allows for filtering content fields, making it easier to query specific content fields using GraphQL. This enhancement relies on having the `OrchardCore.ContentFields.Indexing.SQL` module enabled. + +To filter content items based on a specific content field, you can use a query like this: + +```graphql +product(where: {price: {amount_gt: 10}}) { + contentItemId + displayText + price { + amount + } +} +``` + ### Email Module The `OrchardCore.Email` module has undergone a refactoring process with no breaking changes. However, there are compile-time warnings that are recommended to be addressed. Here is a summary of the changes: From 837eb66a3155bb56af43808ae574a7833f71fda1 Mon Sep 17 00:00:00 2001 From: mdameer Date: Fri, 7 Jun 2024 00:38:10 +0300 Subject: [PATCH 22/29] Make clearing and populating the tenantAliases thread-safe --- .../Types/DynamicContentFieldsIndexAliasProvider.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 08f67c06458..1680a19b6f0 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -32,7 +32,10 @@ public DynamicContentFieldsIndexAliasProvider(IContentDefinitionManager contentD public IEnumerable GetAliases() { - var tenantAliases = _aliases.GetOrAdd(_shellSettings.Name, _ => []); + if (!_aliases.TryGetValue(_shellSettings.Name, out var tenantAliases)) + { + _aliases.TryAdd(_shellSettings.Name, tenantAliases = []); + } if (tenantAliases.Count != 0) { @@ -81,7 +84,10 @@ public IEnumerable GetAliases() private void ClearAliases() { - _aliases.GetOrAdd(_shellSettings.Name, _ => []).Clear(); + if (_aliases.TryRemove(_shellSettings.Name, out var tenantAliases)) + { + tenantAliases.Clear(); + } } public void ContentFieldAttached(ContentFieldAttachedContext context) => ClearAliases(); From 60e1f830671cc47d846f0c743346e3028acc3ffa Mon Sep 17 00:00:00 2001 From: mdameer Date: Fri, 7 Jun 2024 12:45:00 +0300 Subject: [PATCH 23/29] Fix clearing and populating the tenantAliases to be thread-safe --- .../DynamicContentFieldsIndexAliasProvider.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 1680a19b6f0..92fb1f2a0f4 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -32,16 +32,12 @@ public DynamicContentFieldsIndexAliasProvider(IContentDefinitionManager contentD public IEnumerable GetAliases() { - if (!_aliases.TryGetValue(_shellSettings.Name, out var tenantAliases)) - { - _aliases.TryAdd(_shellSettings.Name, tenantAliases = []); - } - - if (tenantAliases.Count != 0) - { - return tenantAliases; - } + return _aliases.GetOrAdd(_shellSettings.Name, _ => GetAliasesInternal()); + } + private List GetAliasesInternal() + { + var tenantAliases = new List(); var types = _contentDefinitionManager.ListTypeDefinitionsAsync().GetAwaiter().GetResult(); var parts = types.SelectMany(t => t.Parts); @@ -84,10 +80,7 @@ public IEnumerable GetAliases() private void ClearAliases() { - if (_aliases.TryRemove(_shellSettings.Name, out var tenantAliases)) - { - tenantAliases.Clear(); - } + _aliases.TryRemove(_shellSettings.Name, out _); } public void ContentFieldAttached(ContentFieldAttachedContext context) => ClearAliases(); From 7945f06a22602173d8702ff9f7b5d34796d3cd95 Mon Sep 17 00:00:00 2001 From: mdameer Date: Sat, 8 Jun 2024 17:44:16 +0300 Subject: [PATCH 24/29] Implement IFilterInputObjectGraphType to unify filter object graph types --- .../Queries/WhereInputObjectGraphType.cs | 20 +++++---- .../Queries/Types/ContentItemType.cs | 7 +-- .../Queries/Types/ContentItemWhereInput.cs | 34 ++++++--------- .../Types/DynamicContentTypeBuilder.cs | 43 ++++++++----------- .../Types/DynamicContentTypeQueryBuilder.cs | 2 +- .../DynamicContentTypeWhereInputBuilder.cs | 2 +- .../Types/DynamicPartWhereInputGraphType.cs | 3 +- .../Types/IContentItemObjectGraphType.cs | 13 ------ 8 files changed, 46 insertions(+), 78 deletions(-) delete mode 100644 src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs diff --git a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs index 66826332d25..076fb63c34c 100644 --- a/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs +++ b/src/OrchardCore/OrchardCore.Apis.GraphQL.Abstractions/Queries/WhereInputObjectGraphType.cs @@ -5,11 +5,18 @@ namespace OrchardCore.Apis.GraphQL.Queries { - public class WhereInputObjectGraphType : WhereInputObjectGraphType + public interface IFilterInputObjectGraphType : IInputObjectGraphType + { + void AddScalarFilterFields(string fieldName, string description); + + void AddScalarFilterFields(Type graphType, string fieldName, string description); + } + + public class WhereInputObjectGraphType : WhereInputObjectGraphType, IFilterInputObjectGraphType { } - public class WhereInputObjectGraphType : InputObjectGraphType + public class WhereInputObjectGraphType : InputObjectGraphType, IFilterInputObjectGraphType { // arguments of typed input graph types return typed object, without additional input fields (_in, _contains,..) // so we return dictionary as it was before. @@ -52,17 +59,12 @@ public override object ParseDictionary(IDictionary value) {"_not_ends_with", "does not end with the string"}, }; - public void AddScalarFilterFields(FieldType fieldType) - { - AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); - } - - public void AddScalarFilterFields(string fieldName, string description) + public virtual void AddScalarFilterFields(string fieldName, string description) { AddScalarFilterFields(typeof(TGraphType), fieldName, description); } - public void AddScalarFilterFields(Type graphType, string fieldName, string description) + public virtual void AddScalarFilterFields(Type graphType, string fieldName, string description) { if (!typeof(ScalarGraphType).IsAssignableFrom(graphType) && !typeof(IInputObjectGraphType).IsAssignableFrom(graphType)) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs index 8f2b69ab696..c16e5e39b20 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemType.cs @@ -13,7 +13,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types { - public class ContentItemType : ObjectGraphType, IContentItemObjectGraphType + public class ContentItemType : ObjectGraphType { private readonly GraphQLContentOptions _options; @@ -58,11 +58,6 @@ public override FieldType AddField(FieldType fieldType) return null; } - public void AddOutputField(FieldType fieldType) - { - AddField(fieldType); - } - private static async ValueTask RenderShapeAsync(IResolveFieldContext context) { var serviceProvider = context.RequestServices; diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs index 0a524eabc4a..55cd0e6fefc 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/ContentItemWhereInput.cs @@ -10,7 +10,7 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries.Types [GraphQLFieldName("Or", "OR")] [GraphQLFieldName("And", "AND")] [GraphQLFieldName("Not", "NOT")] - public class ContentItemWhereInput : WhereInputObjectGraphType, IContentItemInputObjectGraphType + public class ContentItemWhereInput : WhereInputObjectGraphType { private readonly IOptions _optionsAccessor; @@ -22,14 +22,14 @@ public ContentItemWhereInput(string contentItemName, IOptions("contentItemId", "content item id"); - AddFilterField("contentItemVersionId", "the content item version id"); - AddFilterField("displayText", "the display text of the content item"); - AddFilterField("createdUtc", "the date and time of creation"); - AddFilterField("modifiedUtc", "the date and time of modification"); - AddFilterField("publishedUtc", "the date and time of publication"); - AddFilterField("owner", "the owner of the content item"); - AddFilterField("author", "the author of the content item"); + AddScalarFilterFields("contentItemId", "content item id"); + AddScalarFilterFields("contentItemVersionId", "the content item version id"); + AddScalarFilterFields("displayText", "the display text of the content item"); + AddScalarFilterFields("createdUtc", "the date and time of creation"); + AddScalarFilterFields("modifiedUtc", "the date and time of modification"); + AddScalarFilterFields("publishedUtc", "the date and time of publication"); + AddScalarFilterFields("owner", "the owner of the content item"); + AddScalarFilterFields("author", "the author of the content item"); var whereInputType = new ListGraphType(this); Field>("Or").Description("OR logical operation").Type(whereInputType); @@ -37,21 +37,11 @@ public ContentItemWhereInput(string contentItemName, IOptions>("Not").Description("NOT logical operation").Type(whereInputType); } - public void AddInputField(FieldType fieldType) + public override void AddScalarFilterFields(Type graphType, string fieldName, string description) { - AddFilterField(fieldType.Type, fieldType.Name, fieldType.Description); - } - - private void AddFilterField(string name, string description) - { - AddFilterField(typeof(TGraphType), name, description); - } - - private void AddFilterField(Type graphType, string name, string description) - { - if (!_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), name)) + if (!_optionsAccessor.Value.ShouldSkip(typeof(ContentItemType), fieldName)) { - AddScalarFilterFields(graphType, name, description); + base.AddScalarFilterFields(graphType, fieldName, description); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index ec77eb8c1c5..44dc8160071 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; +using OrchardCore.Apis.GraphQL.Queries; using OrchardCore.ContentManagement.GraphQL.Options; using OrchardCore.ContentManagement.Metadata.Models; @@ -31,17 +32,7 @@ public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, public abstract void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType); - protected void BuildObjectGraphType(ISchema schema, ContentTypeDefinition contentTypeDefinition, IContentItemObjectGraphType objectGraphType) - { - BuildInternal(schema, contentTypeDefinition, objectGraphType); - } - - protected void BuildInputObjectGraphType(ISchema schema, ContentTypeDefinition contentTypeDefinition, IContentItemInputObjectGraphType inputObjectGraphType) - { - BuildInternal(schema, contentTypeDefinition, inputObjectGraphType); - } - - private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefinition, IComplexGraphType graphType) + protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefinition, IComplexGraphType graphType) { if (_contentOptions.ShouldHide(contentTypeDefinition)) { @@ -88,16 +79,16 @@ private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefi continue; } - if (graphType is IContentItemInputObjectGraphType _inputGraphType) + if (graphType is IFilterInputObjectGraphType _inputGraphType) { if (fieldProvider.HasFieldIndex(field)) { - _inputGraphType.AddInputField(contentFieldType); + _inputGraphType.AddScalarFilterFields(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); } } - else if (graphType is IContentItemObjectGraphType _graphType) + else if (graphType is IObjectGraphType _graphType) { - _graphType.AddOutputField(contentFieldType); + _graphType.AddField(contentFieldType); } break; @@ -121,16 +112,16 @@ private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefi if (contentFieldType != null && !graphType.HasField(contentFieldType.Name)) { - if (graphType is IContentItemInputObjectGraphType _inputGraphType) + if (graphType is IFilterInputObjectGraphType _inputGraphType) { if (fieldProvider.HasFieldIndex(field)) { - _inputGraphType.AddInputField(contentFieldType); + _inputGraphType.AddScalarFilterFields(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); } } - else if (graphType is IContentItemObjectGraphType _graphType) + else if (graphType is IObjectGraphType _graphType) { - _graphType.AddOutputField(contentFieldType); + _graphType.AddField(contentFieldType); } break; @@ -143,19 +134,19 @@ private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefi if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) { - if (graphType is IContentItemInputObjectGraphType _inputGraphType) + if (graphType is IFilterInputObjectGraphType _inputGraphType) { - _inputGraphType.AddInputField(fieldType); + _inputGraphType.AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); } - else if (graphType is IContentItemObjectGraphType _graphType) + else if (graphType is IObjectGraphType _graphType) { - _graphType.AddOutputField(fieldType); + _graphType.AddField(fieldType); } continue; } - if (graphType is IContentItemInputObjectGraphType inputGraphType) + if (graphType is IFilterInputObjectGraphType inputGraphType) { if (part.PartDefinition.Fields.Any(field => contentFieldProviders.Any(cfp => cfp.HasFieldIndex(field)))) { @@ -163,6 +154,7 @@ private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefi { Name = partName.ToFieldName(), Description = S["Represents a {0}.", part.PartDefinition.Name], + Type = graphType.GetType(), ResolvedType = new DynamicPartWhereInputGraphType(part) }; @@ -170,12 +162,13 @@ private void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDefi _dynamicPartFields[partName] = field; } } - else if (graphType is IContentItemObjectGraphType _graphType) + else if (graphType is IObjectGraphType _graphType) { var field = new FieldType { Name = partName.ToFieldName(), Description = S["Represents a {0}.", part.PartDefinition.Name], + Type = graphType.GetType(), ResolvedType = new DynamicPartGraphType(part), Resolver = new FuncFieldResolver(context => { diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs index 599f2b7f5fb..6a74496498b 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeQueryBuilder.cs @@ -16,6 +16,6 @@ public DynamicContentTypeQueryBuilder(IHttpContextAccessor httpContextAccessor, public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDefinition contentTypeDefinition, ContentItemType contentItemType) { - BuildObjectGraphType(schema, contentTypeDefinition, contentItemType); + BuildInternal(schema, contentTypeDefinition, contentItemType); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs index ac85815ecce..fdde18a5fe5 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeWhereInputBuilder.cs @@ -21,7 +21,7 @@ public override void Build(ISchema schema, FieldType contentQuery, ContentTypeDe if (whereInputType != null) { - BuildInputObjectGraphType(schema, contentTypeDefinition, whereInputType); + BuildInternal(schema, contentTypeDefinition, whereInputType); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs index b91cd75584b..c772841d812 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicPartWhereInputGraphType.cs @@ -28,9 +28,10 @@ public override void Initialize(ISchema schema) foreach (var fieldProvider in contentFieldProviders) { var fieldType = fieldProvider.GetField(schema, field, _part.Name); + if (fieldType != null) { - AddScalarFilterFields(fieldType); + AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); break; } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs deleted file mode 100644 index fc576ae0f0e..00000000000 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/IContentItemObjectGraphType.cs +++ /dev/null @@ -1,13 +0,0 @@ -using GraphQL.Types; - -namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; - -public interface IContentItemObjectGraphType : IObjectGraphType -{ - void AddOutputField(FieldType fieldType); -} - -public interface IContentItemInputObjectGraphType : IInputObjectGraphType -{ - void AddInputField(FieldType fieldType); -} From 9ccfd9fa1234b0515310baac892549d82d123461 Mon Sep 17 00:00:00 2001 From: mdameer Date: Sun, 9 Jun 2024 00:05:23 +0300 Subject: [PATCH 25/29] Update comment for clarity --- .../Queries/Types/DynamicContentTypeBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index df6f6ff481d..423213e71d9 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -106,7 +106,7 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe if (partFieldType != null) { - // Add dynamic content field types to the static part type. + // Add dynamic content fields to the registered part type. var partContentItemType = schema.AdditionalTypeInstances .Where(type => type is IObjectGraphType || type is IFilterInputObjectGraphType) .Where(type => type.GetType() == partFieldType.Type) From 2781ca6b68079734ed47a5796e2837391d4bebea Mon Sep 17 00:00:00 2001 From: Mohammad Dameer Date: Mon, 10 Jun 2024 20:00:49 +0300 Subject: [PATCH 26/29] DynamicContentTypeBuilder variables name refactoring Co-authored-by: Georg von Kries --- .../Types/DynamicContentTypeBuilder.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 423213e71d9..29bca83df9a 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -80,16 +80,16 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe continue; } - if (graphType is IFilterInputObjectGraphType _inputGraphType) + if (graphType is IFilterInputObjectGraphType curInputGraphType) { if (fieldProvider.HasFieldIndex(field)) { - _inputGraphType.AddScalarFilterFields(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); + curInputGraphType.AddScalarFilterFields(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); } } - else if (graphType is IObjectGraphType _graphType) + else if (graphType is IObjectGraphType curObjectGraphType) { - _graphType.AddField(contentFieldType); + curObjectGraphType.AddField(contentFieldType); } break; @@ -128,16 +128,16 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe continue; } - if (partContentItemType is IFilterInputObjectGraphType _partInputContentItemType) + if (partContentItemType is IFilterInputObjectGraphType partInputContentItemType) { if (fieldProvider.HasFieldIndex(field)) { - _partInputContentItemType.AddScalarFilterFields(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); + partInputContentItemType.AddScalarFilterFields(contentFieldType.Type, contentFieldType.Name, contentFieldType.Description); } } - else if (partContentItemType is IObjectGraphType _partContentItemType) + else if (partContentItemType is IObjectGraphType partContentItemObjectType) { - _partContentItemType.AddField(contentFieldType); + partContentItemObjectType.AddField(contentFieldType); } break; @@ -151,13 +151,13 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe if (_dynamicPartFields.TryGetValue(partName, out var fieldType)) { - if (graphType is IFilterInputObjectGraphType _inputGraphType) + if (graphType is IFilterInputObjectGraphType curInputGraphType) { - _inputGraphType.AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); + curInputGraphType.AddScalarFilterFields(fieldType.Type, fieldType.Name, fieldType.Description); } - else if (graphType is IObjectGraphType _graphType) + else if (graphType is IObjectGraphType curObjectGraphType) { - _graphType.AddField(fieldType); + curObjectGraphType.AddField(fieldType); } continue; @@ -179,7 +179,7 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe _dynamicPartFields[partName] = field; } } - else if (graphType is IObjectGraphType _graphType) + else if (graphType is IObjectGraphType objectGraphType) { var field = new FieldType { @@ -196,7 +196,7 @@ protected void BuildInternal(ISchema schema, ContentTypeDefinition contentTypeDe }) }; - _graphType.AddField(field); + objectGraphType.AddField(field); _dynamicPartFields[partName] = field; } } From da07f14a3dd25479521ebffd3e02917eb6dfc905 Mon Sep 17 00:00:00 2001 From: mdameer Date: Mon, 10 Jun 2024 20:04:41 +0300 Subject: [PATCH 27/29] Mahe DynamicContentTypeBuilder ctor protected --- .../Queries/Types/DynamicContentTypeBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs index 29bca83df9a..49b72c65965 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentTypeBuilder.cs @@ -19,7 +19,7 @@ public abstract class DynamicContentTypeBuilder : IContentTypeBuilder protected readonly IStringLocalizer S; private readonly Dictionary _dynamicPartFields; - public DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, + protected DynamicContentTypeBuilder(IHttpContextAccessor httpContextAccessor, IOptions contentOptionsAccessor, IStringLocalizer localizer) { From 60168e4c140bc4fe73d28f56b888eb45f4f3c855 Mon Sep 17 00:00:00 2001 From: mdameer Date: Wed, 12 Jun 2024 00:03:48 +0300 Subject: [PATCH 28/29] Make IIndexAliasProvider.GetAliases async and rename it to GetAliasesAsync, use memory cache instead of concurrentdictionary --- .../GraphQL/AliasPartIndexAliasProvider.cs | 5 +- .../AutoroutePartIndexAliasProvider.cs | 5 +- .../LocalizationPartIndexAliasProvider.cs | 5 +- .../ContainedPartIndexAliasProvider.cs | 5 +- .../Queries/ContentItemsFieldType.cs | 6 +- .../Queries/IIndexAliasProvider.cs | 3 +- .../DynamicContentFieldsIndexAliasProvider.cs | 61 +++++++++---------- .../core/Apis.GraphQL.Abstractions/README.md | 4 +- src/docs/releases/2.0.0.md | 4 ++ 9 files changed, 52 insertions(+), 46 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Alias/GraphQL/AliasPartIndexAliasProvider.cs b/src/OrchardCore.Modules/OrchardCore.Alias/GraphQL/AliasPartIndexAliasProvider.cs index 0235f9ed916..3e6b18dc6ae 100644 --- a/src/OrchardCore.Modules/OrchardCore.Alias/GraphQL/AliasPartIndexAliasProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Alias/GraphQL/AliasPartIndexAliasProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using OrchardCore.Alias.Indexes; using OrchardCore.ContentManagement.GraphQL.Queries; @@ -16,9 +17,9 @@ public class AliasPartIndexAliasProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Autoroute/GraphQL/AutoroutePartIndexAliasProvider.cs b/src/OrchardCore.Modules/OrchardCore.Autoroute/GraphQL/AutoroutePartIndexAliasProvider.cs index 6d16a37b598..81b6407c6bf 100644 --- a/src/OrchardCore.Modules/OrchardCore.Autoroute/GraphQL/AutoroutePartIndexAliasProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Autoroute/GraphQL/AutoroutePartIndexAliasProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using OrchardCore.Autoroute.Core.Indexes; using OrchardCore.ContentManagement.GraphQL.Queries; @@ -16,9 +17,9 @@ public class AutoroutePartIndexAliasProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationPartIndexAliasProvider.cs b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationPartIndexAliasProvider.cs index 1698831e281..6e904e89fa4 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationPartIndexAliasProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/GraphQL/LocalizationPartIndexAliasProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using OrchardCore.ContentLocalization.Records; using OrchardCore.ContentManagement.GraphQL.Queries; @@ -16,9 +17,9 @@ public class LocalizationPartIndexAliasProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ContainedPartIndexAliasProvider.cs b/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ContainedPartIndexAliasProvider.cs index 5b4a1017f1f..0640a59e123 100644 --- a/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ContainedPartIndexAliasProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Lists/GraphQL/ContainedPartIndexAliasProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using OrchardCore.ContentManagement.GraphQL.Queries; using OrchardCore.Lists.Indexes; @@ -16,9 +17,9 @@ public class ContainedPartIndexAliasProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs index 7b3b47b6522..7ceeacf0c7c 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemsFieldType.cs @@ -92,7 +92,7 @@ private async ValueTask> ResolveAsync(IResolveFieldCont query = FilterContentType(query, context); query = OrderBy(query, context); - var contentItemsQuery = FilterWhereArguments(query, where, context, session); + var contentItemsQuery = await FilterWhereArgumentsAsync(query, where, context, session); contentItemsQuery = PageQuery(contentItemsQuery, context); var contentItems = await contentItemsQuery.ListAsync(); @@ -105,7 +105,7 @@ private async ValueTask> ResolveAsync(IResolveFieldCont return contentItems; } - private IQuery FilterWhereArguments( + private async ValueTask> FilterWhereArgumentsAsync( IQuery query, JsonObject where, IResolveFieldContext fieldContext, @@ -133,7 +133,7 @@ private IQuery FilterWhereArguments( foreach (var aliasProvider in providers) { - foreach (var alias in aliasProvider.GetAliases()) + foreach (var alias in await aliasProvider.GetAliasesAsync()) { predicateQuery.CreateAlias(alias.Alias, alias.Index); if (indexAliases.TryAdd(alias.Alias, alias.Alias)) diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IIndexAliasProvider.cs index eab519f8e13..85479ce7f83 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/IIndexAliasProvider.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using System.Threading.Tasks; namespace OrchardCore.ContentManagement.GraphQL.Queries { public interface IIndexAliasProvider { - IEnumerable GetAliases(); + ValueTask> GetAliasesAsync(); } } diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs index 92fb1f2a0f4..db883fa9388 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/Types/DynamicContentFieldsIndexAliasProvider.cs @@ -1,44 +1,44 @@ -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using GraphQL; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using OrchardCore.ContentManagement.GraphQL.Options; using OrchardCore.ContentManagement.Metadata; using OrchardCore.ContentTypes.Events; -using OrchardCore.Environment.Shell; namespace OrchardCore.ContentManagement.GraphQL.Queries.Types; public class DynamicContentFieldsIndexAliasProvider : IIndexAliasProvider, IContentDefinitionEventHandler { - private static readonly ConcurrentDictionary> _aliases = new ConcurrentDictionary>(); + private static string _cacheKey = nameof(DynamicContentFieldsIndexAliasProvider); private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IEnumerable _contentFieldProviders; - private readonly ShellSettings _shellSettings; + private readonly IMemoryCache _memoryCache; private readonly GraphQLContentOptions _contentOptions; public DynamicContentFieldsIndexAliasProvider(IContentDefinitionManager contentDefinitionManager, IEnumerable contentFieldProviders, IOptions contentOptionsAccessor, - ShellSettings shellSettings) + IMemoryCache memoryCache) { _contentDefinitionManager = contentDefinitionManager; _contentFieldProviders = contentFieldProviders; + _memoryCache = memoryCache; _contentOptions = contentOptionsAccessor.Value; - _shellSettings = shellSettings; } - public IEnumerable GetAliases() + public async ValueTask> GetAliasesAsync() { - return _aliases.GetOrAdd(_shellSettings.Name, _ => GetAliasesInternal()); + return await _memoryCache.GetOrCreateAsync(_cacheKey, async _ => await GetAliasesInternalAsync()); } - private List GetAliasesInternal() + private async ValueTask> GetAliasesInternalAsync() { - var tenantAliases = new List(); - var types = _contentDefinitionManager.ListTypeDefinitionsAsync().GetAwaiter().GetResult(); + var aliases = new List(); + var types = await _contentDefinitionManager.ListTypeDefinitionsAsync(); var parts = types.SelectMany(t => t.Parts); foreach (var part in parts) @@ -63,7 +63,7 @@ private List GetAliasesInternal() continue; } - tenantAliases.Add(new IndexAlias + aliases.Add(new IndexAlias { Alias = alias, Index = fieldIndex.Index, @@ -75,45 +75,42 @@ private List GetAliasesInternal() } } - return tenantAliases; + return aliases; } - private void ClearAliases() - { - _aliases.TryRemove(_shellSettings.Name, out _); - } + private void InvalidateInternalAsync() => _memoryCache.Remove(_cacheKey); - public void ContentFieldAttached(ContentFieldAttachedContext context) => ClearAliases(); + public void ContentFieldAttached(ContentFieldAttachedContext context) => InvalidateInternalAsync(); - public void ContentFieldDetached(ContentFieldDetachedContext context) => ClearAliases(); + public void ContentFieldDetached(ContentFieldDetachedContext context) => InvalidateInternalAsync(); - public void ContentPartAttached(ContentPartAttachedContext context) => ClearAliases(); + public void ContentPartAttached(ContentPartAttachedContext context) => InvalidateInternalAsync(); - public void ContentPartCreated(ContentPartCreatedContext context) => ClearAliases(); + public void ContentPartCreated(ContentPartCreatedContext context) => InvalidateInternalAsync(); - public void ContentPartDetached(ContentPartDetachedContext context) => ClearAliases(); + public void ContentPartDetached(ContentPartDetachedContext context) => InvalidateInternalAsync(); - public void ContentPartImported(ContentPartImportedContext context) => ClearAliases(); + public void ContentPartImported(ContentPartImportedContext context) => InvalidateInternalAsync(); public void ContentPartImporting(ContentPartImportingContext context) { } - public void ContentPartRemoved(ContentPartRemovedContext context) => ClearAliases(); + public void ContentPartRemoved(ContentPartRemovedContext context) => InvalidateInternalAsync(); - public void ContentTypeCreated(ContentTypeCreatedContext context) => ClearAliases(); + public void ContentTypeCreated(ContentTypeCreatedContext context) => InvalidateInternalAsync(); - public void ContentTypeImported(ContentTypeImportedContext context) => ClearAliases(); + public void ContentTypeImported(ContentTypeImportedContext context) => InvalidateInternalAsync(); public void ContentTypeImporting(ContentTypeImportingContext context) { } - public void ContentTypeRemoved(ContentTypeRemovedContext context) => ClearAliases(); + public void ContentTypeRemoved(ContentTypeRemovedContext context) => InvalidateInternalAsync(); - public void ContentTypeUpdated(ContentTypeUpdatedContext context) => ClearAliases(); + public void ContentTypeUpdated(ContentTypeUpdatedContext context) => InvalidateInternalAsync(); - public void ContentPartUpdated(ContentPartUpdatedContext context) => ClearAliases(); + public void ContentPartUpdated(ContentPartUpdatedContext context) => InvalidateInternalAsync(); - public void ContentTypePartUpdated(ContentTypePartUpdatedContext context) => ClearAliases(); + public void ContentTypePartUpdated(ContentTypePartUpdatedContext context) => InvalidateInternalAsync(); - public void ContentFieldUpdated(ContentFieldUpdatedContext context) => ClearAliases(); + public void ContentFieldUpdated(ContentFieldUpdatedContext context) => InvalidateInternalAsync(); - public void ContentPartFieldUpdated(ContentPartFieldUpdatedContext context) => ClearAliases(); + public void ContentPartFieldUpdated(ContentPartFieldUpdatedContext context) => InvalidateInternalAsync(); } diff --git a/src/docs/reference/core/Apis.GraphQL.Abstractions/README.md b/src/docs/reference/core/Apis.GraphQL.Abstractions/README.md index 500da2a89b7..1bd36b6b67c 100644 --- a/src/docs/reference/core/Apis.GraphQL.Abstractions/README.md +++ b/src/docs/reference/core/Apis.GraphQL.Abstractions/README.md @@ -249,9 +249,9 @@ public class AutoroutePartIndexAliasProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } } ``` diff --git a/src/docs/releases/2.0.0.md b/src/docs/releases/2.0.0.md index 10acd5752ab..a3eb6e9c31f 100644 --- a/src/docs/releases/2.0.0.md +++ b/src/docs/releases/2.0.0.md @@ -246,10 +246,14 @@ Here are the updated signatures: These adjustments ensure compatibility and adherence to the latest conventions within the `SectionDisplayDriver` class. +### GraphQL Module + The GraphQL schema may change because fields are now always added to the correct part. Previously, additional fields may have been added to the parent content item type directly. You may have to adjust your GraphQL queries in that case. +Additionally, the `GetAliases` method in the `IIndexAliasProvider` interface is now asynchronous and has been renamed to `GetAliasesAsync`. Implementations of this interface should be modified by updating the method signature and ensure they handle asynchronous operations correctly. + ## Change Logs ### Azure AI Search Module From b2ae3ec76925220e71f16ff3c44a74add56b2913 Mon Sep 17 00:00:00 2001 From: mdameer Date: Wed, 12 Jun 2024 00:27:16 +0300 Subject: [PATCH 29/29] Fix tests to renamed GetAliasesAsync --- .../Apis/GraphQL/ContentItemsFieldTypeTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs index e64eb6713da..e45a0f058d2 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentItemsFieldTypeTests.cs @@ -544,9 +544,9 @@ public class MultipleAliasIndexProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } } @@ -574,9 +574,9 @@ public class MultipleIndexesIndexProvider : IIndexAliasProvider } ]; - public IEnumerable GetAliases() + public ValueTask> GetAliasesAsync() { - return _aliases; + return ValueTask.FromResult>(_aliases); } }