Skip to content

Commit 6247f54

Browse files
authored
Adds ancestor ID details on document tree and collection responses (#18909)
* Populate ancestor keys on document tree response items. * Populate ancestor keys on document collection response items. * Update OpenApi.json * Use array of objects rather than Ids for the ancestor collection. * Update OpenApi.json.
1 parent 3e6b931 commit 6247f54

File tree

9 files changed

+101
-5
lines changed

9 files changed

+101
-5
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/DocumentTreeControllerBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Umbraco.Cms.Api.Management.Factories;
55
using Umbraco.Cms.Api.Management.Routing;
66
using Umbraco.Cms.Api.Management.Services.Entities;
7+
using Umbraco.Cms.Api.Management.ViewModels;
78
using Umbraco.Cms.Api.Management.ViewModels.Tree;
89
using Umbraco.Cms.Core;
910
using Umbraco.Cms.Core.Cache;
@@ -33,7 +34,7 @@ protected DocumentTreeControllerBase(
3334
AppCaches appCaches,
3435
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
3536
IDocumentPresentationFactory documentPresentationFactory)
36-
: base(entityService, userStartNodeEntitiesService, dataTypeService)
37+
: base(entityService, userStartNodeEntitiesService, dataTypeService)
3738
{
3839
_publicAccessService = publicAccessService;
3940
_appCaches = appCaches;
@@ -52,6 +53,8 @@ protected override DocumentTreeItemResponseModel MapTreeItemViewModel(Guid? pare
5253
if (entity is IDocumentEntitySlim documentEntitySlim)
5354
{
5455
responseModel.IsProtected = _publicAccessService.IsProtected(entity.Path);
56+
responseModel.Ancestors = EntityService.GetPathKeys(entity, omitSelf: true)
57+
.Select(x => new ReferenceByIdModel(x));
5558
responseModel.IsTrashed = entity.Trashed;
5659
responseModel.Id = entity.Key;
5760
responseModel.CreateDate = entity.CreateDate;

src/Umbraco.Cms.Api.Management/Factories/DocumentCollectionPresentationFactory.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Umbraco.Cms.Api.Management.ViewModels;
13
using Umbraco.Cms.Api.Management.ViewModels.Document;
24
using Umbraco.Cms.Api.Management.ViewModels.Document.Collection;
5+
using Umbraco.Cms.Core.DependencyInjection;
36
using Umbraco.Cms.Core.Mapping;
47
using Umbraco.Cms.Core.Models;
58
using Umbraco.Cms.Core.Services;
@@ -9,11 +12,23 @@ namespace Umbraco.Cms.Api.Management.Factories;
912
public class DocumentCollectionPresentationFactory : ContentCollectionPresentationFactory<IContent, DocumentCollectionResponseModel, DocumentValueResponseModel, DocumentVariantResponseModel>, IDocumentCollectionPresentationFactory
1013
{
1114
private readonly IPublicAccessService _publicAccessService;
15+
private readonly IEntityService _entityService;
1216

17+
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
1318
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService)
14-
: base(mapper)
19+
: this(
20+
mapper,
21+
publicAccessService,
22+
StaticServiceProvider.Instance.GetRequiredService<IEntityService>())
23+
{
24+
}
25+
26+
[ActivatorUtilitiesConstructor]
27+
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService, IEntityService entityService)
28+
: base(mapper)
1529
{
1630
_publicAccessService = publicAccessService;
31+
_entityService = entityService;
1732
}
1833

1934
protected override Task SetUnmappedProperties(ListViewPagedModel<IContent> contentCollection, List<DocumentCollectionResponseModel> collectionResponseModels)
@@ -27,6 +42,8 @@ protected override Task SetUnmappedProperties(ListViewPagedModel<IContent> conte
2742
}
2843

2944
item.IsProtected = _publicAccessService.IsProtected(matchingContentItem).Success;
45+
item.Ancestors = _entityService.GetPathKeys(matchingContentItem, omitSelf: true)
46+
.Select(x => new ReferenceByIdModel(x));
3047
}
3148

3249
return Task.CompletedTask;

src/Umbraco.Cms.Api.Management/Mapping/Document/DocumentMapDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private void Map(IContent source, PublishedDocumentResponseModel target, MapperC
6767
target.IsTrashed = source.Trashed;
6868
}
6969

70-
// Umbraco.Code.MapAll -IsProtected
70+
// Umbraco.Code.MapAll -IsProtected -Ancestors
7171
private void Map(IContent source, DocumentCollectionResponseModel target, MapperContext context)
7272
{
7373
target.Id = source.Key;

src/Umbraco.Cms.Api.Management/OpenApi.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37088,6 +37088,7 @@
3708837088
},
3708937089
"DocumentCollectionResponseModel": {
3709037090
"required": [
37091+
"ancestors",
3709137092
"documentType",
3709237093
"id",
3709337094
"isProtected",
@@ -37143,6 +37144,16 @@
3714337144
"isProtected": {
3714437145
"type": "boolean"
3714537146
},
37147+
"ancestors": {
37148+
"type": "array",
37149+
"items": {
37150+
"oneOf": [
37151+
{
37152+
"$ref": "#/components/schemas/ReferenceByIdModel"
37153+
}
37154+
]
37155+
}
37156+
},
3714637157
"updater": {
3714737158
"type": "string",
3714837159
"nullable": true
@@ -37456,6 +37467,7 @@
3745637467
},
3745737468
"DocumentTreeItemResponseModel": {
3745837469
"required": [
37470+
"ancestors",
3745937471
"createDate",
3746037472
"documentType",
3746137473
"hasChildren",
@@ -37495,6 +37507,16 @@
3749537507
"isProtected": {
3749637508
"type": "boolean"
3749737509
},
37510+
"ancestors": {
37511+
"type": "array",
37512+
"items": {
37513+
"oneOf": [
37514+
{
37515+
"$ref": "#/components/schemas/ReferenceByIdModel"
37516+
}
37517+
]
37518+
}
37519+
},
3749837520
"documentType": {
3749937521
"oneOf": [
3750037522
{

src/Umbraco.Cms.Api.Management/ViewModels/Document/Collection/DocumentCollectionResponseModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ public class DocumentCollectionResponseModel : ContentCollectionResponseModelBas
1111

1212
public bool IsProtected { get; set; }
1313

14+
public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];
15+
1416
public string? Updater { get; set; }
1517
}

src/Umbraco.Cms.Api.Management/ViewModels/Tree/DocumentTreeItemResponseModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Umbraco.Cms.Api.Management.ViewModels.Content;
21
using Umbraco.Cms.Api.Management.ViewModels.Document;
32
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
43

@@ -8,6 +7,8 @@ public class DocumentTreeItemResponseModel : ContentTreeItemResponseModel
87
{
98
public bool IsProtected { get; set; }
109

10+
public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];
11+
1112
public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new();
1213

1314
public IEnumerable<DocumentVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<DocumentVariantItemResponseModel>();

src/Umbraco.Core/Services/EntityService.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Globalization;
12
using System.Linq.Expressions;
23
using Microsoft.Extensions.Logging;
34
using Umbraco.Cms.Core.Events;
@@ -758,5 +759,26 @@ public IEnumerable<IEntitySlim> GetPagedChildren(
758759
return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuids, pageNumber, pageSize, out totalRecords, filter, ordering);
759760
}
760761
}
761-
}
762762

763+
/// <inheritdoc/>>
764+
public Guid[] GetPathKeys(ITreeEntity entity, bool omitSelf = false)
765+
{
766+
IEnumerable<int> ids = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
767+
.Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val) ? val : -1)
768+
.Where(x => x != -1);
769+
770+
Guid[] keys = ids
771+
.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.Document))
772+
.Where(x => x.Success)
773+
.Select(x => x.Result)
774+
.ToArray();
775+
776+
if (omitSelf)
777+
{
778+
// Omit the last path key as that will be for the item itself.
779+
return keys.Take(keys.Length - 1).ToArray();
780+
}
781+
782+
return keys;
783+
}
784+
}

src/Umbraco.Core/Services/IEntityService.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,4 +382,12 @@ IEnumerable<IEntitySlim> GetPagedDescendants(
382382
/// <returns>The identifier.</returns>
383383
/// <remarks>When a new content or a media is saved with the key, it will have the reserved identifier.</remarks>
384384
int ReserveId(Guid key);
385+
386+
/// <summary>
387+
/// Gets the GUID keys for an entity's path (provided as a comma separated list of integer Ids).
388+
/// </summary>
389+
/// <param name="entity">The entity.</param>
390+
/// <param name="omitSelf">A value indicating whether to omit the entity's own key from the result.</param>
391+
/// <returns>The path with each ID converted to a GUID.</returns>
392+
Guid[] GetPathKeys(ITreeEntity entity, bool omitSelf = false) => [];
385393
}

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,27 @@ public void ReserveId()
910910
Assert.IsFalse(EntityService.GetId(Guid.NewGuid(), UmbracoObjectTypes.DocumentType).Success);
911911
}
912912

913+
[Test]
914+
public void EntityService_GetPathKeys_ReturnsExpectedKeys()
915+
{
916+
var contentType = ContentTypeService.Get("umbTextpage");
917+
918+
var root = ContentBuilder.CreateSimpleContent(contentType);
919+
ContentService.Save(root);
920+
921+
var child = ContentBuilder.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root);
922+
ContentService.Save(child);
923+
var grandChild = ContentBuilder.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), child);
924+
ContentService.Save(grandChild);
925+
926+
var result = EntityService.GetPathKeys(grandChild);
927+
Assert.AreEqual($"{root.Key},{child.Key},{grandChild.Key}", string.Join(",", result));
928+
929+
var result2 = EntityService.GetPathKeys(grandChild, omitSelf: true);
930+
Assert.AreEqual($"{root.Key},{child.Key}", string.Join(",", result2));
931+
932+
}
933+
913934
private static bool _isSetup;
914935

915936
private int _folderId;

0 commit comments

Comments
 (0)