Compare commits

...
2 changed files with 187 additions and 4 deletions
@@ -59,7 +59,14 @@ internal sealed class DeliveryApiContentIndexHandleContentChanges : DeliveryApiC
RemoveFromIndex(pendingRemovals, index);
pendingRemovals.Clear();
Reindex(content, index);
IndexUpdateKind indexResult = Reindex(content, index);
// refreshBranch can also mean the whole branch was deleted, this way we know the node was updated and we still need to reindex descendants
// This happens when the node is already published and we do a publishbranch
if (indexResult == IndexUpdateKind.Updated && changeTypes.HasType(TreeChangeTypes.RefreshBranch))
{
ReindexDescendants(content, index);
}
}
}
@@ -68,7 +75,7 @@ internal sealed class DeliveryApiContentIndexHandleContentChanges : DeliveryApiC
return Task.CompletedTask;
});
private void Reindex(IContent content, IIndex index)
private IndexUpdateKind Reindex(IContent content, IIndex index)
{
// get the currently indexed cultures for the content
CulturePublishStatus[] existingCultures = index
@@ -95,16 +102,19 @@ internal sealed class DeliveryApiContentIndexHandleContentChanges : DeliveryApiC
// we likely got here because a removal triggered a "refresh branch" notification, now we
// need to delete every last culture of this content and all descendants
RemoveFromIndex(content.Id, index);
return;
return IndexUpdateKind.Removed;
}
// if the published state changed of any culture, chances are there are similar changes ot the content descendants
// if the published state changed of any culture, chances are there are similar changes at the content descendants
// that need to be reflected in the index, so we'll reindex all descendants
var changedCulturePublishStatus = indexedCultures.Intersect(existingCultures).Count() != existingCultures.Length;
if (changedCulturePublishStatus)
{
ReindexDescendants(content, index);
return IndexUpdateKind.UpdatedWithDescendants;
}
return IndexUpdateKind.Updated;
}
private CulturePublishStatus[] UpdateIndex(IContent content, IIndex index)
@@ -179,4 +189,11 @@ internal sealed class DeliveryApiContentIndexHandleContentChanges : DeliveryApiC
public override int GetHashCode() => HashCode.Combine(Culture, Published);
}
private enum IndexUpdateKind
{
Updated,
UpdatedWithDescendants,
Removed,
}
}
@@ -0,0 +1,166 @@
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Examine;
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
[TestFixture]
public class DeliveryApiContentIndexHandleContentChangesTests : ExamineBaseTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
private IContentService ContentService => GetRequiredService<IContentService>();
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
base.CustomTestSetup(builder);
builder.AddDeliveryApi();
builder.Services.Configure<DeliveryApiSettings>(settings => settings.Enabled = true);
}
/// <summary>
/// Test for https://github.com/umbraco/Umbraco-CMS/issues/20370
/// </summary>
[Test]
public async Task BranchContentIsIndexed()
{
// setup
// setup doc structure without publishing
var doctype = await CreateNestedTextPageContentType();
var documents = CreateDocumentStructure(doctype);
// Publish branch root
ContentService.Publish(documents[0], []);
// act
await ExecuteAndWaitForIndexing(
() => ContentService.PublishBranch(documents[0], PublishBranchFilter.IncludeUnpublished, []),
Constants.UmbracoIndexes.DeliveryApiContentIndexName);
// assert
ExamineManager.TryGetIndex(Constants.UmbracoIndexes.DeliveryApiContentIndexName, out IIndex index);
if (index is null)
{
throw new Exception("DeliveryApiContentIndex not found");
}
foreach (var document in documents)
{
var searchResult = index.Searcher.Search(document.Key.ToString());
}
// all descendants are indexed and marked as published
}
private async Task<IContentType> CreateNestedTextPageContentType()
{
var nestedTextPageContentTypekey = Guid.NewGuid();
var nestedTextPageContentType = new ContentTypeBuilder()
.WithAlias("textPage")
.WithName("Text Page")
.WithKey(nestedTextPageContentTypekey)
.AddAllowedContentType()
.WithKey(nestedTextPageContentTypekey)
.Done()
.AddPropertyGroup()
.WithAlias("content")
.WithName("Content")
.WithSupportsPublishing(true)
.AddPropertyType()
.WithAlias("title")
.WithName("Title")
.Done()
.Done()
.WithAllowAsRoot(true)
.Build();
var result = await ContentTypeService.CreateAsync(nestedTextPageContentType, Constants.Security.SuperUserKey);
if (result.Success is false)
{
throw new Exception("Failed to create content type");
}
return nestedTextPageContentType;
}
private IContent[] CreateDocumentStructure(IContentType doctype)
{
var rootContent = new ContentBuilder()
.WithContentType(doctype)
.WithName("root")
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(rootContent);
var root_1Content = new ContentBuilder()
.WithContentType(doctype)
.WithName("root_1")
.WithParentId(rootContent.Id)
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(root_1Content);
var root_1_1Content = new ContentBuilder()
.WithContentType(doctype)
.WithName("root_1_1")
.WithParentId(root_1Content.Id)
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(root_1_1Content);
var root_1_2Content = new ContentBuilder()
.WithContentType(doctype)
.WithName("root_1_2")
.WithParentId(root_1Content.Id)
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(root_1_2Content);
var root_2Content = new ContentBuilder()
.WithContentType(doctype)
.WithName("root_2")
.WithParentId(rootContent.Id)
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(root_2Content);
var root_2_1Content = new ContentBuilder()
.WithContentType(doctype)
.WithName("root_2_1")
.WithParentId(root_2Content.Id)
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(root_2_1Content);
var root_2_2Content = new ContentBuilder()
.WithContentType(doctype)
.WithName("root_2_2")
.WithParentId(root_2Content.Id)
.WithKey(Guid.NewGuid())
.Build();
ContentService.Save(root_2_2Content);
return
[
rootContent,
root_1Content,
root_1_1Content,
root_1_2Content,
root_2Content,
root_2_1Content,
root_2_2Content
];
}
}