From 7100f9de2572cc67d9f8b6948eb73d111edf53a1 Mon Sep 17 00:00:00 2001 From: ManhOptimizely Date: Tue, 26 Mar 2024 13:14:24 +0700 Subject: [PATCH] FIND-12462: Support link type for Link query - New class LinkQueryBuilder use for build a link query - Add unit test --- .../Api/Querying/BaseTypeQueryBuilder.cs | 70 ++++++++- .../Api/Querying/GraphQueryBuilder.cs | 4 +- .../Api/Querying/LinkQueryBuilder.cs | 37 +++++ .../Api/Querying/TypeQueryBuilder.cs | 12 ++ .../Helpers/Text/StringExtensions.cs | 5 + .../GenerateLinkQueryTests.cs | 140 ++++++++++++++++++ .../GenerateQueryTests.cs | 24 --- 7 files changed, 260 insertions(+), 32 deletions(-) create mode 100644 APIs/src/EpiServer.ContentGraph/Api/Querying/LinkQueryBuilder.cs create mode 100644 APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateLinkQueryTests.cs diff --git a/APIs/src/EpiServer.ContentGraph/Api/Querying/BaseTypeQueryBuilder.cs b/APIs/src/EpiServer.ContentGraph/Api/Querying/BaseTypeQueryBuilder.cs index 8c1fefff..408f636e 100644 --- a/APIs/src/EpiServer.ContentGraph/Api/Querying/BaseTypeQueryBuilder.cs +++ b/APIs/src/EpiServer.ContentGraph/Api/Querying/BaseTypeQueryBuilder.cs @@ -1,4 +1,5 @@ using EPiServer.ContentGraph.Helpers; +using EPiServer.ContentGraph.Helpers.Text; using GraphQL.Transport; using System; @@ -64,22 +65,79 @@ public virtual BaseTypeQueryBuilder Field(string propertyName) return this; } - public virtual BaseTypeQueryBuilder Link(BaseTypeQueryBuilder link) + public virtual BaseTypeQueryBuilder Link(ITypeQueryBuilder link) { link.ValidateNotNullArgument("link"); - string linkItems = link.GetQuery()?.Query ?? string.Empty; - if (!linkItems.IsNullOrEmpty()) + var linkQueryBuilder = link as ILinkQueryBuilder; + if (linkQueryBuilder is null) { + throw new ArgumentException("The argument [link] is not type of [LinkQueryBuilder]"); + } + string linkQuery = linkQueryBuilder.GetQuery()?.Query ?? string.Empty; + if (!linkQuery.IsNullOrEmpty()) + { + if (!linkQueryBuilder.GetLinkType().IsNullOrEmpty()) + { + graphObject.SelectItems.Append( + graphObject.SelectItems.Length == 0 ? + $"_link(type:{linkQueryBuilder.GetLinkType()})" : + $" _link(type:{linkQueryBuilder.GetLinkType()})" + ); + } graphObject.SelectItems.Append( graphObject.SelectItems.Length == 0 ? - $"_link{{{linkItems}}}" : - $" _link{{{linkItems}}}" + $"{{{linkQuery}}}" : + $" {{{linkQuery}}}" ); } return this; } + public virtual BaseTypeQueryBuilder Link(ITypeQueryBuilder link, string alias) + { + link.ValidateNotNullArgument("link"); + var linkQueryBuilder = link as ILinkQueryBuilder; + if (linkQueryBuilder is null) + { + throw new ArgumentException("The argument [link] is not type of [LinkQueryBuilder]"); + } + if (!alias.IsValidName(50)) + { + throw new ArgumentException($"Alias name {alias} is not valid"); + } + string linkQuery = linkQueryBuilder.GetQuery()?.Query ?? string.Empty; + if (!linkQuery.IsNullOrEmpty()) + { + if (!linkQueryBuilder.GetLinkType().IsNullOrEmpty()) + { + if (string.IsNullOrEmpty(alias)) + { + graphObject.SelectItems.Append( + graphObject.SelectItems.Length == 0 ? + $"_link(type:{linkQueryBuilder.GetLinkType()})" : + $" _link(type:{linkQueryBuilder.GetLinkType()})" + ); + } + else + { + graphObject.SelectItems.Append( + graphObject.SelectItems.Length == 0 ? + $"{alias}:_link(type:{linkQueryBuilder.GetLinkType()})" : + $" {alias}:_link(type:{linkQueryBuilder.GetLinkType()})" + ); + } + + } + graphObject.SelectItems.Append( + graphObject.SelectItems.Length == 0 ? + $"{{{linkQuery}}}" : + $" {{{linkQuery}}}" + ); + + } + return this; + } [Obsolete("Use Link method instead")] - public virtual BaseTypeQueryBuilder Children(BaseTypeQueryBuilder children) + public virtual BaseTypeQueryBuilder Children(ITypeQueryBuilder children) { children.ValidateNotNullArgument("children"); string childrenItems = children.GetQuery()?.Query ?? string.Empty; diff --git a/APIs/src/EpiServer.ContentGraph/Api/Querying/GraphQueryBuilder.cs b/APIs/src/EpiServer.ContentGraph/Api/Querying/GraphQueryBuilder.cs index 58843fb9..8577fb23 100644 --- a/APIs/src/EpiServer.ContentGraph/Api/Querying/GraphQueryBuilder.cs +++ b/APIs/src/EpiServer.ContentGraph/Api/Querying/GraphQueryBuilder.cs @@ -15,6 +15,7 @@ using System.Linq; using System.IO; using System.Collections.Generic; +using EPiServer.ContentGraph.Helpers.Text; namespace EPiServer.ContentGraph.Api.Querying { @@ -113,8 +114,7 @@ public IEnumerable GetFragments() /// public GraphQueryBuilder OperationName(string op) { - Regex reg = new Regex(@"^[a-zA-Z_]\w*$"); - if (reg.IsMatch(op)) + if (op.IsValidName()) { _query.OperationName = op; } diff --git a/APIs/src/EpiServer.ContentGraph/Api/Querying/LinkQueryBuilder.cs b/APIs/src/EpiServer.ContentGraph/Api/Querying/LinkQueryBuilder.cs new file mode 100644 index 00000000..adc22332 --- /dev/null +++ b/APIs/src/EpiServer.ContentGraph/Api/Querying/LinkQueryBuilder.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EPiServer.ContentGraph.Api.Querying +{ + public interface ILinkQueryBuilder : ITypeQueryBuilder + { + string GetLinkType(); + } + + public class LinkQueryBuilder : TypeQueryBuilder, ILinkQueryBuilder + { + private string _type; + public LinkQueryBuilder() : base() + { + } + public LinkQueryBuilder(string linkType) : base() + { + _type = linkType; + } + public LinkQueryBuilder WithLinkType(string linkType) + { + if (_type == null) + { + _type = linkType; + } + return this; + } + public string GetLinkType() + { + return _type; + } + } +} diff --git a/APIs/src/EpiServer.ContentGraph/Api/Querying/TypeQueryBuilder.cs b/APIs/src/EpiServer.ContentGraph/Api/Querying/TypeQueryBuilder.cs index 4db12177..acc15cb9 100644 --- a/APIs/src/EpiServer.ContentGraph/Api/Querying/TypeQueryBuilder.cs +++ b/APIs/src/EpiServer.ContentGraph/Api/Querying/TypeQueryBuilder.cs @@ -33,6 +33,18 @@ public TypeQueryBuilder Link(TypeQueryBuilder link) base.Link(link); return this; } + /// + /// Link with simple alias + /// + /// + /// Length should lte 50, can not start with numbers + /// + /// + public TypeQueryBuilder Link(TypeQueryBuilder link, string alias) + { + base.Link(link, alias); + return this; + } [Obsolete("Use Link method instead")] public TypeQueryBuilder Children(TypeQueryBuilder children) { diff --git a/APIs/src/EpiServer.ContentGraph/Helpers/Text/StringExtensions.cs b/APIs/src/EpiServer.ContentGraph/Helpers/Text/StringExtensions.cs index 8e8a0b3e..0cace558 100644 --- a/APIs/src/EpiServer.ContentGraph/Helpers/Text/StringExtensions.cs +++ b/APIs/src/EpiServer.ContentGraph/Helpers/Text/StringExtensions.cs @@ -264,5 +264,10 @@ public static bool IsWildcardPrefix(this string value) return false; } + public static bool IsValidName(this string name, int length=25) + { + Regex reg = new Regex(@"^[a-zA-Z_]\w*$"); + return reg.IsMatch(name) && name.Length <= length; + } } } diff --git a/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateLinkQueryTests.cs b/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateLinkQueryTests.cs new file mode 100644 index 00000000..33119d47 --- /dev/null +++ b/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateLinkQueryTests.cs @@ -0,0 +1,140 @@ +using EpiServer.ContentGraph.UnitTests.QueryTypeObjects; +using EPiServer.ContentGraph.Api.Filters; +using EPiServer.ContentGraph.Api.Querying; +using Xunit; + +namespace EpiServer.ContentGraph.UnitTests +{ + public class GenerateLinkQueryTests + { + private TypeQueryBuilder typeQueryBuilder; + public GenerateLinkQueryTests() + { + typeQueryBuilder = new TypeQueryBuilder(); + } + + [Fact] + public void LinkQueryTests() + { + string childQuery = "SubTypeObject(where:{SubProperty:{match: \"test\"}}){items{SubProperty} facets{Property3{name count}}}"; + string expectedFields = $"items{{Property1 Property2 _link(type:CUSTOMERREFERENCES) {{{childQuery}}}}}"; + string expectedFacets = @"facets{Property3{NestedProperty{name count}}}"; + var linkQuery = new LinkQueryBuilder("CUSTOMERREFERENCES") + .Field(x => x.SubProperty) + .Where(x => x.SubProperty, new StringFilterOperators().Match("test")) + .Facet(x => x.Property3); + + typeQueryBuilder + .Field(x => x.Property1) + .Field(x => x.Property2) + .Link(linkQuery) + .Facet(x => x.Property3.NestedProperty); + GraphQueryBuilder query = typeQueryBuilder.ToQuery(); + + Assert.NotNull(query.GetQuery()); + Assert.Contains(expectedFacets, query.GetQuery().Query); + Assert.Contains(expectedFields, query.GetQuery().Query); + Assert.Equal($"RequestTypeObject{{{expectedFields} {expectedFacets}}}", query.GetQuery().Query); + } + + [Fact] + public void multiple_links_query_should_generate_correct_query() + { + string expectedLink1 = "SubTypeObject(where:{SubProperty:{match: \"test1\"}}){items{SubProperty} facets{Property3{name count}}}"; + string expectedLink2 = "SubTypeObject(where:{Property1:{match: \"test2\"}}){items{Property1} facets{Property3{name count}}}"; + string expectedFields = $"items{{Property1 Property2 _link(type:CUSTOMERREFERENCES) {{{expectedLink1}}} _link(type:DEFAULT) {{{expectedLink2}}}}}"; + string expectedFacets = @"facets{Property3{NestedProperty{name count}}}"; + var linkQuery1 = new LinkQueryBuilder("CUSTOMERREFERENCES") + .Field(x => x.SubProperty) + .Where(x => x.SubProperty, new StringFilterOperators().Match("test1")) + .Facet(x => x.Property3); + + var linkQuery2 = new LinkQueryBuilder("DEFAULT") + .Field(x => x.Property1) + .Where(x => x.Property1, new StringFilterOperators().Match("test2")) + .Facet(x => x.Property3); + + typeQueryBuilder + .Field(x => x.Property1) + .Field(x => x.Property2) + .Link(linkQuery1) + .Link(linkQuery2) + .Facet(x => x.Property3.NestedProperty); + GraphQueryBuilder query = typeQueryBuilder.ToQuery(); + + Assert.Equal(linkQuery1.GetQuery().Query, expectedLink1); + Assert.Equal(linkQuery2.GetQuery().Query, expectedLink2); + + Assert.Contains(expectedFacets, query.GetQuery().Query); + Assert.Contains(expectedFields, query.GetQuery().Query); + Assert.Equal($"RequestTypeObject{{{expectedFields} {expectedFacets}}}", query.GetQuery().Query); + } + + [Fact] + public void nested_links_query_should_generate_nested_link_query() + { + string expectedLink1 = "SubTypeObject(where:{SubProperty:{match: \"test1\"}}){items{SubProperty} facets{Property3{name count}}}"; + string expectedLink2 = "SubTypeObject(where:{Property1:{match: \"test2\"}}){items{Property1 _link(type:CUSTOMERREFERENCES) {" + expectedLink1 + "}} facets{Property3{name count}}}"; + string expectedFields = $"items{{Property1 Property2 _link(type:DEFAULT) {{{expectedLink2}}}}}"; + string expectedFacets = @"facets{Property3{NestedProperty{name count}}}"; + var linkQuery1 = new LinkQueryBuilder() + .WithLinkType("CUSTOMERREFERENCES") + .Field(x => x.SubProperty) + .Where(x => x.SubProperty, new StringFilterOperators().Match("test1")) + .Facet(x => x.Property3); + + var linkQuery2 = new LinkQueryBuilder("DEFAULT") + .Field(x => x.Property1) + .Where(x => x.Property1, new StringFilterOperators().Match("test2")) + .Facet(x => x.Property3) + .Link(linkQuery1); + + typeQueryBuilder + .Field(x => x.Property1) + .Field(x => x.Property2) + .Link(linkQuery2) + .Facet(x => x.Property3.NestedProperty); + GraphQueryBuilder query = typeQueryBuilder.ToQuery(); + + Assert.Equal(linkQuery1.GetQuery().Query, expectedLink1); + Assert.Equal(linkQuery2.GetQuery().Query, expectedLink2); + + Assert.Contains(expectedFacets, query.GetQuery().Query); + Assert.Contains(expectedFields, query.GetQuery().Query); + Assert.Equal($"RequestTypeObject{{{expectedFields} {expectedFacets}}}", query.GetQuery().Query); + } + [Fact] + public void nested_link_query_with_aliases() + { + string expectedLink1 = "SubTypeObject(where:{SubProperty:{match: \"test1\"}}){items{SubProperty} facets{Property3{name count}}}"; + string expectedLink2 = "SubTypeObject(where:{Property1:{match: \"test2\"}}){items{Property1 mylink1:_link(type:CUSTOMERREFERENCES) {" + expectedLink1 + "}} facets{Property3{name count}}}"; + string expectedFields = $"items{{Property1 Property2 mylink2:_link(type:DEFAULT) {{{expectedLink2}}}}}"; + string expectedFacets = @"facets{Property3{NestedProperty{name count}}}"; + var linkQuery1 = new LinkQueryBuilder() + .WithLinkType("CUSTOMERREFERENCES") + .Field(x => x.SubProperty) + .Where(x => x.SubProperty, new StringFilterOperators().Match("test1")) + .Facet(x => x.Property3); + + var linkQuery2 = new LinkQueryBuilder("DEFAULT") + .Field(x => x.Property1) + .Where(x => x.Property1, new StringFilterOperators().Match("test2")) + .Facet(x => x.Property3) + .Link(linkQuery1, "mylink1"); + + typeQueryBuilder + .Field(x => x.Property1) + .Field(x => x.Property2) + .Link(linkQuery2, "mylink2") + .Facet(x => x.Property3.NestedProperty); + GraphQueryBuilder query = typeQueryBuilder.ToQuery(); + + Assert.Equal(linkQuery1.GetQuery().Query, expectedLink1); + Assert.Equal(linkQuery2.GetQuery().Query, expectedLink2); + + Assert.Contains(expectedFacets, query.GetQuery().Query); + Assert.Contains(expectedFields, query.GetQuery().Query); + Assert.Equal($"RequestTypeObject{{{expectedFields} {expectedFacets}}}", query.GetQuery().Query); + } + } +} diff --git a/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateQueryTests.cs b/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateQueryTests.cs index 5edfc840..c574a30a 100644 --- a/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateQueryTests.cs +++ b/APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateQueryTests.cs @@ -170,30 +170,6 @@ public void SubtypeQueryTests() Assert.Equal($"RequestTypeObject{{{expectedFields} {expectedFacets}}}", query.GetQuery().Query); } - [Fact] - public void LinkQueryTests() - { - string childQuery = "SubTypeObject(where:{SubProperty:{match: \"test\"}}){items{SubProperty} facets{Property3{name count}}}"; - string expectedFields = $"items{{Property1 Property2 _link{{{childQuery}}}}}"; - string expectedFacets = @"facets{Property3{NestedProperty{name count}}}"; - TypeQueryBuilder linkQuery = new TypeQueryBuilder() - .Field(x => x.SubProperty) - .Where(x => x.SubProperty, new StringFilterOperators().Match("test")) - .Facet(x => x.Property3); - - typeQueryBuilder - .Field(x => x.Property1) - .Field(x => x.Property2) - .Link(linkQuery) - .Facet(x => x.Property3.NestedProperty); - GraphQueryBuilder query = typeQueryBuilder.ToQuery(); - - Assert.NotNull(query.GetQuery()); - Assert.Contains(expectedFacets, query.GetQuery().Query); - Assert.Contains(expectedFields, query.GetQuery().Query); - Assert.Equal($"RequestTypeObject{{{expectedFields} {expectedFacets}}}", query.GetQuery().Query); - } - [Fact] public void Multiple_types_query() {