Skip to content

Adds ancestor identifiers on document tree and collection responses #18909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
Expand Down Expand Up @@ -33,7 +34,7 @@ protected DocumentTreeControllerBase(
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, userStartNodeEntitiesService, dataTypeService)
: base(entityService, userStartNodeEntitiesService, dataTypeService)
{
_publicAccessService = publicAccessService;
_appCaches = appCaches;
Expand All @@ -52,6 +53,8 @@ protected override DocumentTreeItemResponseModel MapTreeItemViewModel(Guid? pare
if (entity is IDocumentEntitySlim documentEntitySlim)
{
responseModel.IsProtected = _publicAccessService.IsProtected(entity.Path);
responseModel.Ancestors = EntityService.GetPathKeys(entity, omitSelf: true)
.Select(x => new ReferenceByIdModel(x));
responseModel.IsTrashed = entity.Trashed;
responseModel.Id = entity.Key;
responseModel.CreateDate = entity.CreateDate;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.Document.Collection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
Expand All @@ -9,11 +12,23 @@ namespace Umbraco.Cms.Api.Management.Factories;
public class DocumentCollectionPresentationFactory : ContentCollectionPresentationFactory<IContent, DocumentCollectionResponseModel, DocumentValueResponseModel, DocumentVariantResponseModel>, IDocumentCollectionPresentationFactory
{
private readonly IPublicAccessService _publicAccessService;
private readonly IEntityService _entityService;

[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in V17.")]
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService)
: base(mapper)
: this(
mapper,
publicAccessService,
StaticServiceProvider.Instance.GetRequiredService<IEntityService>())
{
}

[ActivatorUtilitiesConstructor]
public DocumentCollectionPresentationFactory(IUmbracoMapper mapper, IPublicAccessService publicAccessService, IEntityService entityService)
: base(mapper)
{
_publicAccessService = publicAccessService;
_entityService = entityService;
}

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

item.IsProtected = _publicAccessService.IsProtected(matchingContentItem).Success;
item.Ancestors = _entityService.GetPathKeys(matchingContentItem, omitSelf: true)
.Select(x => new ReferenceByIdModel(x));
}

return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private void Map(IContent source, PublishedDocumentResponseModel target, MapperC
target.IsTrashed = source.Trashed;
}

// Umbraco.Code.MapAll -IsProtected
// Umbraco.Code.MapAll -IsProtected -Ancestors
private void Map(IContent source, DocumentCollectionResponseModel target, MapperContext context)
{
target.Id = source.Key;
Expand Down
22 changes: 22 additions & 0 deletions src/Umbraco.Cms.Api.Management/OpenApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -37088,6 +37088,7 @@
},
"DocumentCollectionResponseModel": {
"required": [
"ancestors",
"documentType",
"id",
"isProtected",
Expand Down Expand Up @@ -37143,6 +37144,16 @@
"isProtected": {
"type": "boolean"
},
"ancestors": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
]
}
},
"updater": {
"type": "string",
"nullable": true
Expand Down Expand Up @@ -37456,6 +37467,7 @@
},
"DocumentTreeItemResponseModel": {
"required": [
"ancestors",
"createDate",
"documentType",
"hasChildren",
Expand Down Expand Up @@ -37495,6 +37507,16 @@
"isProtected": {
"type": "boolean"
},
"ancestors": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
]
}
},
"documentType": {
"oneOf": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public class DocumentCollectionResponseModel : ContentCollectionResponseModelBas

public bool IsProtected { get; set; }

public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];

public string? Updater { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Umbraco.Cms.Api.Management.ViewModels.Content;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;

Expand All @@ -8,6 +7,8 @@ public class DocumentTreeItemResponseModel : ContentTreeItemResponseModel
{
public bool IsProtected { get; set; }

public IEnumerable<ReferenceByIdModel> Ancestors { get; set; } = [];

public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new();

public IEnumerable<DocumentVariantItemResponseModel> Variants { get; set; } = Enumerable.Empty<DocumentVariantItemResponseModel>();
Expand Down
24 changes: 23 additions & 1 deletion src/Umbraco.Core/Services/EntityService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Globalization;

Check notice on line 1 in src/Umbraco.Core/Services/EntityService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

✅ Getting better: Primitive Obsession

The ratio of primitive types in function arguments decreases from 55.63% to 55.56%, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
using System.Linq.Expressions;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Events;
Expand Down Expand Up @@ -758,5 +759,26 @@
return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuids, pageNumber, pageSize, out totalRecords, filter, ordering);
}
}
}

/// <inheritdoc/>>
public Guid[] GetPathKeys(ITreeEntity entity, bool omitSelf = false)
{
IEnumerable<int> ids = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
.Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val) ? val : -1)
.Where(x => x != -1);

Guid[] keys = ids
.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.Document))
.Where(x => x.Success)
.Select(x => x.Result)
.ToArray();

if (omitSelf)
{
// Omit the last path key as that will be for the item itself.
return keys.Take(keys.Length - 1).ToArray();
}

return keys;
}
}
8 changes: 8 additions & 0 deletions src/Umbraco.Core/Services/IEntityService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Umbraco.Cms.Core.Models;

Check notice on line 1 in src/Umbraco.Core/Services/IEntityService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

✅ Getting better: Missing Arguments Abstractions

The average number of function arguments decreases from 5.50 to 5.11, threshold = 4.00. The functions in this file have too many arguments, indicating a lack of encapsulation or too many responsibilities in the same functions. Avoid adding more.

Check notice on line 1 in src/Umbraco.Core/Services/IEntityService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

✅ Getting better: Primitive Obsession

The ratio of primitive types in function arguments decreases from 54.55% to 54.35%, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Persistence.Querying;

Expand Down Expand Up @@ -382,4 +382,12 @@
/// <returns>The identifier.</returns>
/// <remarks>When a new content or a media is saved with the key, it will have the reserved identifier.</remarks>
int ReserveId(Guid key);

/// <summary>
/// Gets the GUID keys for an entity's path (provided as a comma separated list of integer Ids).
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="omitSelf">A value indicating whether to omit the entity's own key from the result.</param>
/// <returns>The path with each ID converted to a GUID.</returns>
Guid[] GetPathKeys(ITreeEntity entity, bool omitSelf = false) => [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,27 @@ public void ReserveId()
Assert.IsFalse(EntityService.GetId(Guid.NewGuid(), UmbracoObjectTypes.DocumentType).Success);
}

[Test]
public void EntityService_GetPathKeys_ReturnsExpectedKeys()
{
var contentType = ContentTypeService.Get("umbTextpage");

var root = ContentBuilder.CreateSimpleContent(contentType);
ContentService.Save(root);

var child = ContentBuilder.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root);
ContentService.Save(child);
var grandChild = ContentBuilder.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), child);
ContentService.Save(grandChild);

var result = EntityService.GetPathKeys(grandChild);
Assert.AreEqual($"{root.Key},{child.Key},{grandChild.Key}", string.Join(",", result));

var result2 = EntityService.GetPathKeys(grandChild, omitSelf: true);
Assert.AreEqual($"{root.Key},{child.Key}", string.Join(",", result2));

}

private static bool _isSetup;

private int _folderId;
Expand Down