Skip to content

Commit 18a3fa7

Browse files
Nested fragments in query
- Extend fragments functionality for nested fragments - Obsolete Children method - Use StringBuilder to build SelectItems
1 parent 4cce869 commit 18a3fa7

File tree

6 files changed

+205
-26
lines changed

6 files changed

+205
-26
lines changed

APIs/src/EpiServer.ContentGraph/Api/Querying/BaseTypeQueryBuilder.cs

+46-15
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public abstract class BaseTypeQueryBuilder : ITypeQueryBuilder
1212
protected IQuery _parent = null;
1313
public virtual IQuery Parent
1414
{
15-
get => _parent;
15+
get => _parent;
1616
set
1717
{
1818
if (_parent.IsNull())
@@ -55,14 +55,11 @@ public virtual BaseTypeQueryBuilder Field(string propertyName)
5555
if (!propertyName.IsNullOrEmpty())
5656
{
5757
string clonedPropName = ConvertNestedFieldToString.ConvertNestedFieldForQuery(propertyName);
58-
if (graphObject.SelectItems.IsNullOrEmpty())
59-
{
60-
graphObject.SelectItems = $"{clonedPropName}";
61-
}
62-
else
63-
{
64-
graphObject.SelectItems += $" {clonedPropName}";
65-
}
58+
graphObject.SelectItems.Append(
59+
graphObject.SelectItems.Length == 0 ?
60+
$"{clonedPropName}" :
61+
$" {clonedPropName}"
62+
);
6663
}
6764

6865
return this;
@@ -73,9 +70,11 @@ public virtual BaseTypeQueryBuilder Link(BaseTypeQueryBuilder link)
7370
string linkItems = link.GetQuery()?.Query ?? string.Empty;
7471
if (!linkItems.IsNullOrEmpty())
7572
{
76-
graphObject.SelectItems += graphObject.SelectItems.IsNullOrEmpty() ?
73+
graphObject.SelectItems.Append(
74+
graphObject.SelectItems.Length == 0 ?
7775
$"_link{{{linkItems}}}" :
78-
$" _link{{{linkItems}}}";
76+
$" _link{{{linkItems}}}"
77+
);
7978
}
8079
return this;
8180
}
@@ -86,9 +85,11 @@ public virtual BaseTypeQueryBuilder Children(BaseTypeQueryBuilder children)
8685
string childrenItems = children.GetQuery()?.Query ?? string.Empty;
8786
if (!childrenItems.IsNullOrEmpty())
8887
{
89-
graphObject.SelectItems += graphObject.SelectItems.IsNullOrEmpty() ?
88+
graphObject.SelectItems.Append(
89+
graphObject.SelectItems.Length == 0 ?
9090
$"_children{{{childrenItems}}}" :
91-
$" _children{{{childrenItems}}}";
91+
$" _children{{{childrenItems}}}"
92+
);
9293
}
9394

9495
return this;
@@ -105,9 +106,39 @@ public virtual BaseTypeQueryBuilder Fragments(params FragmentBuilder[] fragments
105106
protected virtual BaseTypeQueryBuilder Fragment(FragmentBuilder fragment)
106107
{
107108
fragment.ValidateNotNullArgument("fragment");
108-
graphObject.SelectItems += graphObject.SelectItems.IsNullOrEmpty() ? $"...{fragment.GetName()}" : $" ...{fragment.GetName()}";
109-
Parent?.AddFragment(fragment);
109+
graphObject.SelectItems.Append(
110+
graphObject.SelectItems.Length == 0 ?
111+
$"...{fragment.GetName()}" :
112+
$" ...{fragment.GetName()}"
113+
);
114+
115+
if (Parent != null)
116+
{
117+
Parent.AddFragment(fragment);
118+
var children = GetAllChildren(fragment);
119+
foreach (var childFragment in children)
120+
{
121+
Parent.AddFragment(childFragment);
122+
}
123+
}
110124
return this;
111125
}
126+
private IEnumerable<FragmentBuilder> GetAllChildren(FragmentBuilder fragment)
127+
{
128+
if (fragment.HasChildren)
129+
{
130+
foreach (var child in fragment.ChildrenFragments)
131+
{
132+
yield return child;
133+
if (child.HasChildren)
134+
{
135+
foreach (var item in GetAllChildren(child))
136+
{
137+
yield return item;
138+
}
139+
}
140+
}
141+
}
142+
}
112143
}
113144
}

APIs/src/EpiServer.ContentGraph/Api/Querying/ContentGraphQuery.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class ContentGraphQuery
1515
public string Autocomplete { get; set; } = string.Empty;
1616
public string Cursor { get; set; } = string.Empty;
1717
public string TypeName { get; set; } = string.Empty;
18-
public string SelectItems { get; set; } = string.Empty;
18+
public StringBuilder SelectItems { get; set; } = new StringBuilder();
1919
public string Facets { get; set; } = string.Empty;
2020
public string Total { get; set; } = string.Empty;
2121
public override string ToString()
@@ -27,7 +27,7 @@ public override string ToString()
2727
stringBuilder.Append(Filter);
2828
}
2929
stringBuilder.Append('{');
30-
stringBuilder.Append(SelectItems);//mandatory property
30+
stringBuilder.Append(SelectItems);
3131
if (!Facets.IsNullOrEmpty())
3232
{
3333
stringBuilder.Append(' ');

APIs/src/EpiServer.ContentGraph/Api/Querying/FragmentBuilder.cs

+17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public FragmentBuilder<T> Link<TLink>(TypeQueryBuilder<TLink> link)
3434
base.Link(link);
3535
return this;
3636
}
37+
[Obsolete("Obsoleted. Use Link instead")]
3738
public FragmentBuilder<T> Children<TChildren>(TypeQueryBuilder<TChildren> children)
3839
{
3940
base.Children(children);
@@ -48,6 +49,9 @@ public override GraphQLRequest GetQuery()
4849

4950
public class FragmentBuilder : BaseTypeQueryBuilder
5051
{
52+
private List<FragmentBuilder> _childrenFragments;
53+
public IEnumerable<FragmentBuilder> ChildrenFragments => _childrenFragments;
54+
public bool HasChildren => _childrenFragments != null && _childrenFragments.Any();
5155
public FragmentBuilder() : base()
5256
{
5357
_query.OperationName = "sampleFragment";
@@ -64,5 +68,18 @@ public string GetName()
6468
{
6569
return _query.OperationName;
6670
}
71+
public override FragmentBuilder Fragments(params FragmentBuilder[] fragments)
72+
{
73+
if (fragments.IsNotNull() && fragments.Length > 0)
74+
{
75+
if (_childrenFragments.IsNull())
76+
{
77+
_childrenFragments = new List<FragmentBuilder>();
78+
}
79+
base.Fragments(fragments);
80+
_childrenFragments.AddRange(fragments);
81+
}
82+
return this;
83+
}
6784
}
6885
}

APIs/src/EpiServer.ContentGraph/Api/Querying/TypeQueryBuilder.cs

+12-9
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ public TypeQueryBuilder<T> Fields(params Expression<Func<T, object>>[] fieldSele
8686
public TypeQueryBuilder<T> AsType<TSub>(string propertyName) where TSub : T
8787
{
8888
string subTypeName = typeof(TSub).Name;
89-
graphObject.SelectItems = graphObject.SelectItems.IsNullOrEmpty() ?
89+
graphObject.SelectItems.Append(graphObject.SelectItems.Length == 0 ?
9090
$"... on {subTypeName}{{{ConvertNestedFieldToString.ConvertNestedFieldForQuery(propertyName)}}}" :
91-
$"{graphObject.SelectItems} ... on {subTypeName}{{{ConvertNestedFieldToString.ConvertNestedFieldForQuery(propertyName)}}}";
91+
$" ... on {subTypeName}{{{ConvertNestedFieldToString.ConvertNestedFieldForQuery(propertyName)}}}"
92+
);
9293
return this;
9394
}
9495
/// <summary>
@@ -111,9 +112,10 @@ public TypeQueryBuilder<T> AsType<TSub>(params Expression<Func<TSub, object>>[]
111112

112113
}
113114
string subTypeName = typeof(TSub).Name;
114-
graphObject.SelectItems = graphObject.SelectItems.IsNullOrEmpty() ?
115+
graphObject.SelectItems.Append(graphObject.SelectItems.Length == 0 ?
115116
$"... on {subTypeName}{{{propertyName}}}" :
116-
$"{graphObject.SelectItems} ... on {subTypeName}{{{propertyName}}}";
117+
$" ... on {subTypeName}{{{propertyName}}}"
118+
);
117119
return this;
118120
}
119121
/// <summary>
@@ -127,9 +129,10 @@ public TypeQueryBuilder<T> AsType<TSub>(SubTypeQueryBuilder<TSub> subTypeQuery)
127129
subTypeQuery.ValidateNotNullArgument("subTypeQuery");
128130
subTypeQuery.Parent = this.Parent;
129131
string subTypeName = typeof(TSub).Name;
130-
graphObject.SelectItems = graphObject.SelectItems.IsNullOrEmpty() ?
132+
graphObject.SelectItems.Append(graphObject.SelectItems.Length == 0 ?
131133
$"... on {subTypeName}{subTypeQuery.GetQuery().Query}" :
132-
$"{graphObject.SelectItems} ... on {subTypeName}{subTypeQuery.GetQuery().Query}";
134+
$" ... on {subTypeName}{subTypeQuery.GetQuery().Query}"
135+
);
133136
return this;
134137
}
135138

@@ -549,7 +552,7 @@ public override GraphQueryBuilder ToQuery()
549552
{
550553
if (!_compiled)
551554
{
552-
if (graphObject.SelectItems.IsNullOrEmpty() && graphObject.Total.IsNullOrEmpty() && graphObject.Facets.IsNullOrEmpty() && graphObject.Autocomplete.IsNullOrEmpty())
555+
if (graphObject.SelectItems?.Length == 0 && graphObject.Total.IsNullOrEmpty() && graphObject.Facets.IsNullOrEmpty() && graphObject.Autocomplete.IsNullOrEmpty())
553556
{
554557
throw new ArgumentNullException("You must select at least one of the values [Field(s), Facet(s), Total, Autocomplete(s)]");
555558
}
@@ -566,9 +569,9 @@ public override GraphQueryBuilder ToQuery()
566569
{
567570
graphObject.Filter = graphObject.Filter.IsNullOrEmpty() ? graphObject.Ids : $"{graphObject.Filter},{graphObject.Ids}";
568571
}
569-
if (!graphObject.SelectItems.IsNullOrEmpty())
572+
if (graphObject.SelectItems.Length > 0)
570573
{
571-
graphObject.SelectItems = $"items{{{graphObject.SelectItems}}}";
574+
graphObject.SelectItems = new System.Text.StringBuilder($"items{{{graphObject.SelectItems}}}");
572575
}
573576

574577
if (!graphObject.WhereClause.IsNullOrEmpty())

APIs/src/Testing/EpiServer.ContentGraph.UnitTests/GenerateFragmentTests.cs

+64
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,69 @@ public void add_fragment_to_a_type_query_should_generate_correct_string()
5050
Assert.Contains(expectedFragmment, graphQueryBuilder.GetQuery().Query);
5151
Assert.Contains(expectedFields, graphQueryBuilder.GetQuery().Query);
5252
}
53+
[Fact]
54+
public void multiple_fragments_should_generate_correct_query()
55+
{
56+
const string expectedMainQuery = "query FragmentTest {FragmentObject{items{Name ...FirstFragment ...SecondFragment}}}";
57+
const string expectedFistFragment = "fragment FirstFragment on PromoObject {ProviderName}";
58+
const string expectedSecondFragmment = "fragment SecondFragment on FragmentObject {ViewingTime PromoImage{Url}}";
59+
const string expectedFullQuery = $"{expectedMainQuery}\n{expectedFistFragment}\n{expectedSecondFragmment}";
60+
61+
var firstFragment = new FragmentBuilder<PromoObject>("FirstFragment");
62+
firstFragment.Fields(x => x.ProviderName);
63+
64+
var secondFragment = new FragmentBuilder<FragmentObject>("SecondFragment");
65+
secondFragment.Fields(x => x.ViewingTime, x=> x.PromoImage.Url);
66+
67+
GraphQueryBuilder graphQueryBuilder = new GraphQueryBuilder();
68+
graphQueryBuilder
69+
.OperationName("FragmentTest")
70+
.ForType<FragmentObject>()
71+
.Field(x => x.Name)
72+
.Fragments(firstFragment, secondFragment)
73+
.ToQuery()
74+
.BuildQueries();
75+
76+
Assert.Equal(graphQueryBuilder.GetFragments().First().GetName(), "FirstFragment");
77+
Assert.Equal(graphQueryBuilder.GetFragments().First().GetQuery().Query, expectedFistFragment);
78+
79+
Assert.Equal(graphQueryBuilder.GetFragments().Last().GetName(), "SecondFragment");
80+
Assert.Equal(graphQueryBuilder.GetFragments().Last().GetQuery().Query, expectedSecondFragmment);
81+
82+
Assert.Equal(expectedFullQuery, graphQueryBuilder.GetQuery().Query);
83+
}
84+
[Fact]
85+
public void nested_fragments_should_generate_correct_query()
86+
{
87+
const string expectedMainQuery = "query FragmentTest {FragmentObject{items{Name ...SecondFragment}}}";
88+
const string expectedFistFragment = "fragment FirstFragment on PromoObject {Expanded{Property1}}";
89+
const string expectedSecondFragmment = "fragment SecondFragment on FragmentObject {PromoText PromoImage{Url} ...FirstFragment}";
90+
const string expectedFullQuery = $"{expectedMainQuery}\n{expectedSecondFragmment}\n{expectedFistFragment}";
91+
92+
var firstFragment = new FragmentBuilder<PromoObject>("FirstFragment");
93+
firstFragment.Fields(x => x.Expanded.Property1);
94+
95+
var secondFragment = new FragmentBuilder<FragmentObject>("SecondFragment");
96+
secondFragment.Fields(x => x.PromoText, x=> x.PromoImage.Url);
97+
secondFragment.Fragments(firstFragment);
98+
99+
GraphQueryBuilder graphQueryBuilder = new GraphQueryBuilder();
100+
graphQueryBuilder
101+
.OperationName("FragmentTest")
102+
.ForType<FragmentObject>()
103+
.Field(x => x.Name)
104+
.Fragments(secondFragment)
105+
.ToQuery()
106+
.BuildQueries();
107+
108+
//expect children in secondary fragment
109+
Assert.Equal(graphQueryBuilder.GetFragments().First().GetName(), "SecondFragment");
110+
Assert.True(graphQueryBuilder.GetFragments().First().HasChildren);
111+
Assert.Equal(graphQueryBuilder.GetFragments().First().ChildrenFragments.First().GetQuery().Query, expectedFistFragment);
112+
//expect secondary fragment
113+
Assert.Equal(graphQueryBuilder.GetFragments().First().GetQuery().Query, expectedSecondFragmment);
114+
//expect full query
115+
Assert.Equal(expectedFullQuery, graphQueryBuilder.GetQuery().Query);
116+
}
53117
}
54118
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace EpiServer.ContentGraph.UnitTests.QueryTypeObjects
8+
{
9+
internal class PromoObject
10+
{
11+
public int Id { get; set; }
12+
public int WorkId { get; set; }
13+
public string GuidValue { get; set; }
14+
public string ProviderName { get; set; }
15+
public string Url { get; set; }
16+
public RequestTypeObject Expanded { get; set; }
17+
}
18+
internal class FragmentObject
19+
{
20+
public virtual IEnumerable<string> ContentType { get; set; }
21+
22+
public virtual string RouteSegment { get; set; }
23+
24+
public virtual string Url { get; set; }
25+
26+
public virtual string RelativePath { get; set; }
27+
28+
public virtual string Status { get; set; }
29+
30+
public virtual IEnumerable<string> Ancestors { get; set; }
31+
32+
33+
public virtual string Name { get; set; }
34+
35+
public string SitePageTheme { get; set; }
36+
37+
38+
public string Heading { get; set; }
39+
40+
41+
public string Preamble { get; set; }
42+
43+
44+
public string PromoHeading { get; set; }
45+
46+
47+
public string PromoText { get; set; }
48+
49+
50+
public PromoObject PromoImage { get; set; } = new();
51+
52+
53+
public IEnumerable<PromoObject> Categories { get; set; }
54+
55+
56+
public PromoObject CompanyContentLink { get; set; } = new();
57+
58+
public string ReadingTime { get; set; }
59+
60+
public string ViewingTime { get; set; }
61+
62+
public DateTime CommonDate { get; set; }
63+
}
64+
}

0 commit comments

Comments
 (0)