Skip to content

Commit ad5a18f

Browse files
BrynjarthBrynjar ÞorsteinssonAndyButland
committed
Fix pagination in Content Delivery API Index Helper (#19606)
* Refactor descendant enumeration in DeliveryApiContentIndexHelper Improved loop condition to allow for processing of more than 10.000 descendants for indexing. * Add failing test for original issue. * Renamed variable for clarity. --------- Co-authored-by: Brynjar Þorsteinsson <[email protected]> Co-authored-by: Andy Butland <[email protected]>
1 parent cdc62d3 commit ad5a18f

File tree

2 files changed

+134
-7
lines changed

2 files changed

+134
-7
lines changed

src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexHelper.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Options;
1+
using Microsoft.Extensions.Options;
22
using Umbraco.Cms.Core.Configuration.Models;
33
using Umbraco.Cms.Core.Models;
44
using Umbraco.Cms.Core.Persistence.Querying;
@@ -28,21 +28,28 @@ public DeliveryApiContentIndexHelper(
2828
public void EnumerateApplicableDescendantsForContentIndex(int rootContentId, Action<IContent[]> actionToPerform)
2929
{
3030
const int pageSize = 10000;
31-
var pageIndex = 0;
31+
EnumerateApplicableDescendantsForContentIndex(rootContentId, actionToPerform, pageSize);
32+
}
33+
34+
internal void EnumerateApplicableDescendantsForContentIndex(int rootContentId, Action<IContent[]> actionToPerform, int pageSize)
35+
{
36+
var itemIndex = 0;
37+
long total;
3238

33-
IContent[] descendants;
3439
IQuery<IContent> query = _umbracoDatabaseFactory.SqlContext.Query<IContent>().Where(content => content.Trashed == false);
40+
41+
IContent[] descendants;
3542
do
3643
{
3744
descendants = _contentService
38-
.GetPagedDescendants(rootContentId, pageIndex, pageSize, out _, query, Ordering.By("Path"))
45+
.GetPagedDescendants(rootContentId, itemIndex / pageSize, pageSize, out total, query, Ordering.By("Path"))
3946
.Where(descendant => _deliveryApiSettings.IsAllowedContentType(descendant.ContentType.Alias))
4047
.ToArray();
4148

42-
actionToPerform(descendants.ToArray());
49+
actionToPerform(descendants);
4350

44-
pageIndex++;
51+
itemIndex += pageSize;
4552
}
46-
while (descendants.Length == pageSize);
53+
while (descendants.Length > 0 && itemIndex < total);
4754
}
4855
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using Microsoft.Extensions.Options;
2+
using Moq;
3+
using NUnit.Framework;
4+
using Umbraco.Cms.Core.Configuration.Models;
5+
using Umbraco.Cms.Core.Models;
6+
using Umbraco.Cms.Core.Services;
7+
using Umbraco.Cms.Infrastructure.Examine;
8+
using Umbraco.Cms.Infrastructure.Persistence;
9+
using Umbraco.Cms.Tests.Common.Builders;
10+
using Umbraco.Cms.Tests.Common.Testing;
11+
using Umbraco.Cms.Tests.Integration.Testing;
12+
13+
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Examine;
14+
15+
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
16+
[TestFixture]
17+
public class DeliveryApiContentIndexHelperTests : UmbracoIntegrationTestWithContent
18+
{
19+
public override void CreateTestData()
20+
{
21+
base.CreateTestData();
22+
23+
// Save an extra, published content item of a different type to those created via the base class,
24+
// that we'll use to test filtering out disallowed content types.
25+
var template = TemplateBuilder.CreateTextPageTemplate("textPage2");
26+
FileService.SaveTemplate(template);
27+
28+
var contentType = ContentTypeBuilder.CreateSimpleContentType("umbTextpage2", "Textpage2", defaultTemplateId: template.Id);
29+
contentType.Key = Guid.NewGuid();
30+
ContentTypeService.Save(contentType);
31+
32+
ContentType.AllowedContentTypes =
33+
[
34+
new ContentTypeSort(ContentType.Key, 0, "umbTextpage"),
35+
new ContentTypeSort(contentType.Key, 1, "umbTextpage2"),
36+
];
37+
ContentTypeService.Save(ContentType);
38+
39+
var subpage = ContentBuilder.CreateSimpleContent(contentType, "Alternate Text Page 4", Textpage.Id);
40+
ContentService.Save(subpage);
41+
42+
// And then add some more of the first type, so the one we'll filter out in tests isn't in the last page.
43+
for (int i = 0; i < 5; i++)
44+
{
45+
subpage = ContentBuilder.CreateSimpleContent(ContentType, $"Text Page {5 + i}", Textpage.Id);
46+
ContentService.Save(subpage);
47+
}
48+
}
49+
50+
[Test]
51+
public void Can_Enumerate_Descendants_For_Content_Index()
52+
{
53+
var sut = CreateDeliveryApiContentIndexHelper();
54+
55+
var expectedNumberOfContentItems = GetExpectedNumberOfContentItems();
56+
57+
var contentEnumerated = 0;
58+
Action<IContent[]> actionToPerform = content =>
59+
{
60+
contentEnumerated += content.Length;
61+
};
62+
63+
const int pageSize = 3;
64+
sut.EnumerateApplicableDescendantsForContentIndex(
65+
Cms.Core.Constants.System.Root,
66+
actionToPerform,
67+
pageSize);
68+
69+
Assert.AreEqual(expectedNumberOfContentItems, contentEnumerated);
70+
}
71+
72+
[Test]
73+
public void Can_Enumerate_Descendants_For_Content_Index_With_Disallowed_Content_Type()
74+
{
75+
var sut = CreateDeliveryApiContentIndexHelper(["umbTextPage2"]);
76+
77+
var expectedNumberOfContentItems = GetExpectedNumberOfContentItems();
78+
79+
var contentEnumerated = 0;
80+
Action<IContent[]> actionToPerform = content =>
81+
{
82+
contentEnumerated += content.Length;
83+
};
84+
85+
const int pageSize = 3;
86+
sut.EnumerateApplicableDescendantsForContentIndex(
87+
Cms.Core.Constants.System.Root,
88+
actionToPerform,
89+
pageSize);
90+
91+
Assert.AreEqual(expectedNumberOfContentItems - 1, contentEnumerated);
92+
}
93+
94+
private DeliveryApiContentIndexHelper CreateDeliveryApiContentIndexHelper(HashSet<string>? disallowedContentTypeAliases = null)
95+
{
96+
return new DeliveryApiContentIndexHelper(
97+
ContentService,
98+
GetRequiredService<IUmbracoDatabaseFactory>(),
99+
GetDeliveryApiSettings(disallowedContentTypeAliases ?? []));
100+
}
101+
102+
private IOptionsMonitor<DeliveryApiSettings> GetDeliveryApiSettings(HashSet<string> disallowedContentTypeAliases)
103+
{
104+
var deliveryApiSettings = new DeliveryApiSettings
105+
{
106+
DisallowedContentTypeAliases = disallowedContentTypeAliases,
107+
};
108+
109+
var optionsMonitorMock = new Mock<IOptionsMonitor<DeliveryApiSettings>>();
110+
optionsMonitorMock.Setup(o => o.CurrentValue).Returns(deliveryApiSettings);
111+
return optionsMonitorMock.Object;
112+
}
113+
114+
private int GetExpectedNumberOfContentItems()
115+
{
116+
var result = ContentService.GetAllPublished().Count();
117+
Assert.AreEqual(10, result);
118+
return result;
119+
}
120+
}

0 commit comments

Comments
 (0)