Skip to content
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

Moved logic from FolderTreeControllerBase service layer #15976

Merged
merged 4 commits into from Apr 4, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -27,6 +27,17 @@ public DataTypeTreeControllerBase(IEntityService entityService, IDataTypeService

protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.DataTypeContainer;

protected override Ordering ItemOrdering
{
get
{
var ordering = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectType), Direction.Descending); // We need to override to change direction
ordering.Next = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));

return ordering;
}
}

protected override DataTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentId, IEntitySlim[] entities)
{
var dataTypes = _dataTypeService
Expand Down
Expand Up @@ -14,6 +14,20 @@ public abstract class FolderTreeControllerBase<TItem> : NamedEntityTreeControlle
private readonly Guid _folderObjectTypeId;
private bool _foldersOnly;



protected override Ordering ItemOrdering
{
get
{
// Override to order by type (folder vs item) before the text
var ordering = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.NodeObjectType));
ordering.Next = Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));

return ordering;
}
}

protected FolderTreeControllerBase(IEntityService entityService)
: base(entityService) =>
// ReSharper disable once VirtualMemberCallInConstructor
Expand Down Expand Up @@ -76,38 +90,19 @@ private IEntitySlim[] GetEntities(Guid? parentKey, int skip, int take, out long
{
totalItems = 0;

if (take == 0)
{
totalItems = _foldersOnly
? EntityService.CountChildren(parentKey, FolderObjectType)
: EntityService.CountChildren(parentKey, FolderObjectType)
+ EntityService.CountChildren(parentKey, ItemObjectType);
return Array.Empty<IEntitySlim>();
}
UmbracoObjectTypes[] childObjectTypes = _foldersOnly ? [FolderObjectType] : [FolderObjectType, ItemObjectType];

// EntityService is not able to paginate children of multiple item types, so we will only paginate the
// item type entities and always return all folders as part of the the first result "page" i.e. when skip is 0
IEntitySlim[] folderEntities = skip == 0
? EntityService.GetChildren(parentKey, FolderObjectType).OrderBy(c => c.Name).ToArray()
: Array.Empty<IEntitySlim>();
IEntitySlim[] itemEntities = _foldersOnly
? Array.Empty<IEntitySlim>()
: EntityService.GetPagedChildren(
IEntitySlim[] itemEntities = EntityService.GetPagedChildren(
parentKey,
new [] { FolderObjectType, ItemObjectType },
ItemObjectType,
childObjectTypes,
skip,
take,
false,
out totalItems,
ordering: ItemOrdering)
.ToArray();

// the GetChildren for folders does not return an amount and does not get executed when beyond the first page
// but the items still count towards the total, so add these to either 0 when only folders, or the out param from paged
totalItems += skip == 0
? folderEntities.Length
: EntityService.CountChildren(parentKey, FolderObjectType);

return folderEntities.Union(itemEntities).ToArray();
return itemEntities;
}
}
14 changes: 13 additions & 1 deletion src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs
Expand Up @@ -84,7 +84,19 @@ public interface IEntityRepository : IRepository
int pageSize,
out long totalRecords,
IQuery<IUmbracoEntity>? filter,
Ordering? ordering) =>
GetPagedResultsByQuery(query, new HashSet<Guid>(){objectType}, pageIndex, pageSize, out totalRecords, filter, ordering);

IEnumerable<IEntitySlim> GetPagedResultsByQuery(
IQuery<IUmbracoEntity> query,
ISet<Guid> objectTypes,
long pageIndex,
int pageSize,
out long totalRecords,
IQuery<IUmbracoEntity>? filter,
Ordering? ordering);

int CountByQuery(IQuery<IUmbracoEntity> query, Guid objectType, IQuery<IUmbracoEntity>? filter);
int CountByQuery(IQuery<IUmbracoEntity> query, Guid objectType, IQuery<IUmbracoEntity>? filter) =>
CountByQuery(query, new HashSet<Guid>() { objectType }, filter);
int CountByQuery(IQuery<IUmbracoEntity> query, IEnumerable<Guid> objectTypes, IQuery<IUmbracoEntity>? filter);
}
73 changes: 55 additions & 18 deletions src/Umbraco.Core/Services/EntityService.cs
@@ -1,4 +1,4 @@
using System.Linq.Expressions;

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 (v14/dev)

ℹ Getting worse: Primitive Obsession

The ratio of primitive types in function arguments increases from 49.62% to 50.00%, 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 Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
Expand Down Expand Up @@ -396,6 +396,12 @@
return Enumerable.Empty<IEntitySlim>();
}

if (take == 0)
{
totalRecords = CountChildren(parentId, childObjectType, filter);
return Enumerable.Empty<IEntitySlim>();
}

PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize);

IEnumerable<IEntitySlim> children = GetPagedChildren(
Expand Down Expand Up @@ -645,34 +651,23 @@
}
}

public int CountChildren(
private int CountChildren(int id, UmbracoObjectTypes objectType, IQuery<IUmbracoEntity>? filter = null) =>
CountChildren(id, new HashSet<UmbracoObjectTypes>() { objectType }, filter);

private int CountChildren(
int id,
UmbracoObjectTypes objectType,
IEnumerable<UmbracoObjectTypes> objectTypes,
IQuery<IUmbracoEntity>? filter = null)
{
using (ScopeProvider.CreateCoreScope(autoComplete: true))
{
IQuery<IUmbracoEntity> query = Query<IUmbracoEntity>().Where(x => x.ParentId == id && x.Trashed == false);

return _entityRepository.CountByQuery(query, objectType.GetGuid(), filter);
var objectTypeGuids = objectTypes.Select(x => x.GetGuid()).ToHashSet();
return _entityRepository.CountByQuery(query, objectTypeGuids, filter);
}
}

public int CountChildren(Guid? key, UmbracoObjectTypes objectType, IQuery<IUmbracoEntity>? filter = null)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope();

if (ResolveKey(key, objectType, out var parentId) is false)
{
return 0;
}

var count = CountChildren(parentId, objectType, filter);

scope.Complete();
return count;
}

private bool ResolveKey(Guid? key, UmbracoObjectTypes objectType, out int id)
{
// We have to explicitly check for "root key" since this value is null, and GetId does not accept null.
Expand Down Expand Up @@ -721,5 +716,47 @@
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
}
}

public IEnumerable<IEntitySlim> GetPagedChildren(
Guid? parentKey,
IEnumerable<UmbracoObjectTypes> parentObjectTypes,
IEnumerable<UmbracoObjectTypes> childObjectTypes,
int skip,
int take,
bool trashed,
out long totalRecords,
IQuery<IUmbracoEntity>? filter = null,
Ordering? ordering = null)
{
using (ScopeProvider.CreateCoreScope(autoComplete: true))
{
var parentId = 0;
var parentIdResolved = parentObjectTypes.Any(parentObjectType => ResolveKey(parentKey, parentObjectType, out parentId));
if (parentIdResolved is false)
{
totalRecords = 0;
return Enumerable.Empty<IEntitySlim>();
}

if (take == 0)
{
totalRecords = CountChildren(parentId, childObjectTypes, filter);
return Array.Empty<IEntitySlim>();
}

IQuery<IUmbracoEntity> query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId && x.Trashed == trashed);

PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize);

var objectTypeGuids = childObjectTypes.Select(x => x.GetGuid()).ToHashSet();
if (pageSize == 0)
{
totalRecords = _entityRepository.CountByQuery(query, objectTypeGuids, filter);
return Enumerable.Empty<IEntitySlim>();
}

return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuids, pageNumber, pageSize, out totalRecords, filter, ordering);
}
}

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

View check run for this annotation

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

ℹ New issue: Excess Number of Function Arguments

GetPagedChildren has 9 arguments, threshold = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.
}

22 changes: 15 additions & 7 deletions src/Umbraco.Core/Services/IEntityService.cs
Expand Up @@ -233,6 +233,21 @@ IEnumerable<IEntitySlim> GetChildren(Guid? key, UmbracoObjectTypes objectType)
return Array.Empty<IEntitySlim>();
}

IEnumerable<IEntitySlim> GetPagedChildren(
Guid? parentKey,
IEnumerable<UmbracoObjectTypes> parentObjectTypes,
IEnumerable<UmbracoObjectTypes> childObjectTypes,
int skip,
int take,
bool trashed,
out long totalRecords,
IQuery<IUmbracoEntity>? filter = null,
Ordering? ordering = null)
{
totalRecords = 0;
return Array.Empty<IEntitySlim>();
}

/// <summary>
/// Gets children of an entity.
/// </summary>
Expand Down Expand Up @@ -367,11 +382,4 @@ IEnumerable<IEntitySlim> GetChildren(Guid? key, UmbracoObjectTypes objectType)
/// <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>
/// Counts the children of an entity
/// </summary>
int CountChildren(int id, UmbracoObjectTypes objectType, IQuery<IUmbracoEntity>? filter = null);

public int CountChildren(Guid? key, UmbracoObjectTypes objectType, IQuery<IUmbracoEntity>? filter = null) => 0;
}
1 change: 1 addition & 0 deletions src/Umbraco.Core/Services/Ordering.cs
Expand Up @@ -34,6 +34,7 @@ public Ordering(string? orderBy, Direction direction = Direction.Ascending, stri
IsCustomField = isCustomField;
}

public Ordering? Next { get; set; } = null;
/// <summary>
/// Gets the name of the ordering field.
/// </summary>
Expand Down
@@ -1,4 +1,4 @@
using NPoco;

Check notice on line 1 in src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs

View check run for this annotation

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

✅ Getting better: Primitive Obsession

The ratio of primitive types in function arguments decreases from 67.20% to 65.60%, 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;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
Expand Down Expand Up @@ -30,13 +30,13 @@

#region Repository

public int CountByQuery(IQuery<IUmbracoEntity> query, Guid objectType, IQuery<IUmbracoEntity>? filter)
public int CountByQuery(IQuery<IUmbracoEntity> query, IEnumerable<Guid> objectTypes, IQuery<IUmbracoEntity>? filter)
{
Sql<ISqlContext> sql = Sql();
sql.SelectCount();
sql
.From<NodeDto>();
sql.WhereIn<NodeDto>(x => x.NodeObjectType, new[] { objectType } );
sql.WhereIn<NodeDto>(x => x.NodeObjectType, objectTypes );

foreach (Tuple<string, object[]> queryClause in query.GetWhereClauses())
{
Expand All @@ -54,10 +54,11 @@
return Database.ExecuteScalar<int>(sql);
}

public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType,
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, ISet<Guid> objectTypes,
long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity>? filter, Ordering? ordering) =>
GetPagedResultsByQuery(query, new[] {objectType}, pageIndex, pageSize, out totalRecords, filter, ordering);
GetPagedResultsByQuery(query, objectTypes.ToArray(), pageIndex, pageSize, out totalRecords, filter, ordering);


// get a page of entities
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid[] objectTypes,
Expand Down Expand Up @@ -634,26 +635,41 @@

// TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort
// As more things are attempted to be sorted we'll prob have to add more expressions here
string orderBy;
switch (ordering.OrderBy?.ToUpperInvariant())
{
case "PATH":
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
break;

default:
orderBy = ordering.OrderBy ?? string.Empty;
break;
}
Ordering? runner = ordering;

if (ordering.Direction == Direction.Ascending)
do
{
sql.OrderBy(orderBy);
}
else
{
sql.OrderByDescending(orderBy);

switch (runner.OrderBy?.ToUpperInvariant())
{
case "NODEOBJECTTYPE":
orderBy = $"UPPER({SqlSyntax.GetQuotedColumn(NodeDto.TableName, "nodeObjectType")})";
break;
case "PATH":
orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
break;

default:
orderBy = runner.OrderBy ?? string.Empty;
break;
}

if (runner.Direction == Direction.Ascending)
{
sql.OrderBy(orderBy);
}
else
{
sql.OrderByDescending(orderBy);
}

runner = runner.Next;
}
while (runner is not null);


Check warning on line 672 in src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs

View check run for this annotation

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

❌ New issue: Complex Method

ApplyOrdering has a cyclomatic complexity of 9, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

#endregion
Expand Down