New backoffice - trees design (#12963)

* Refactor: Add default versioned back office route attribute

* Tree controller bases and first draft implementations for document, media and doctype

* Move tree item view models to appropriate location

* Fix missing parent

* Refactor user entity access for testability

* A bit of clean-up + handle user start nodes for items endpoint

* Implement foldersOnly for folder tree

* Items endpoint for document type tree

* Strongly typed action results

* Content + media recycle bin

* Correct return type for swagger

* Member type tree

* Rename user start node handling to make a little more sense

* Revert to faked admin start nodes in document tree

* Media type tree

* Data type tree

* Relation type tree

* Remove unused dependency from member type tree

* Correct documentation for member type tree endpoint response types

* Use icon constants

* Add templates tree

* Member group tree

* Document blueprint tree

* Partial views, scripts and stylesheets trees

* Static files tree

* Clarify "folders only" state

* Comments and improved readability

* Rename TreeControllerBase and TreeItemViewModel

* Move recycle bin controller base to its own namespace

* Moved tree base controllers to their own namespace

* Common base class for tree view models

* Remove ProblemDetails response type declaration from all actions

* Add OpenApiTag

* Various review comments

* Dictionary item tree

* Renamed all tree controllers to follow action/feature naming convention

* Handle client culture state for document tree

* Support "ignore user start nodes" for content and media + refactor how tree states work to make things more explicit

* Fix or postpone a few TODOs

* Make entity service able to paginate trashed children

* Handle sorting explicitly

* Re-apply VersionedApiBackOfficeRoute to install and upgrade controllers after merge

* Use PagedViewModel instead of PagedResult for all trees

* Explain the usage of UmbracoObjectTypes.Unknown

* Introduce and apply GetMany pattern for dictionary items

* Add a note about relation type caching

* Fix broken test build + add unit tests for new localization service methods

* Use new management API controller base

* Entity repository should build document entities for document blueprints when getting paged entities (same as it does when getting specific entities)

* Use Media type for Media recycle bin

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Move shared relation service to concrete implementations

* Use inclusive language

* Add 401 response type documentation to applicable trees

* Refactor entity load for folder tree controller base + ensure that folders are only included in the first result page

* Add (in-memory) pagination to dictionary tree

* Make file system controller honor paging parameters

* Support pagination in relation type tree

* Clarify method name a bit for detecting tree root path requests

* Update Open API schema to match new trees

* Move from page number and page size to skip/take (with temporary workaround for lack of property skip/take pagination in current DB implementation)

* Update OpenAPI schema to match skip/take

* Update OpenAPI schema

* Don't return paginated view models from "items" endpoints

* Update OpenApi schema

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Kenn Jacobsen
2022-09-28 13:37:59 +02:00
committed by GitHub
parent 3752f51625
commit 134b193c74
103 changed files with 5976 additions and 255 deletions

View File

@@ -119,11 +119,31 @@ public static partial class Constants
/// </summary>
public const string Packages = "icon-box";
/// <summary>
/// System property editor icon
/// </summary>
public const string PartialView = "icon-article";
/// <summary>
/// System property editor icon
/// </summary>
public const string PropertyEditor = "icon-autofill";
/// <summary>
/// Relation type icon
/// </summary>
public const string RelationType = "icon-trafic";
/// <summary>
/// Script type icon
/// </summary>
public const string Script = "icon-script";
/// <summary>
/// Stylesheet type icon
/// </summary>
public const string Stylesheet = "icon-brackets";
/// <summary>
/// System member icon
/// </summary>

View File

@@ -54,7 +54,7 @@ public enum UmbracoObjectTypes
/// <summary>
/// Member Group
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)]
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup, typeof(IMemberGroup))]
[FriendlyName("Member Group")]
[UmbracoUdiType(Constants.UdiEntityType.MemberGroup)]
MemberGroup,

View File

@@ -6,6 +6,10 @@ public interface IDictionaryRepository : IReadWriteQueryRepository<int, IDiction
{
IDictionaryItem? Get(Guid uniqueId);
IEnumerable<IDictionaryItem> GetMany(params Guid[] uniqueIds) => Array.Empty<IDictionaryItem>();
IEnumerable<IDictionaryItem> GetManyByKeys(params string[] keys) => Array.Empty<IDictionaryItem>();
IDictionaryItem? Get(string key);
IEnumerable<IDictionaryItem> GetDictionaryItemDescendants(Guid? parentId);

View File

@@ -37,6 +37,8 @@ public class EntityService : RepositoryService, IEntityService
{ typeof(IMediaType).FullName!, UmbracoObjectTypes.MediaType },
{ typeof(IMember).FullName!, UmbracoObjectTypes.Member },
{ typeof(IMemberType).FullName!, UmbracoObjectTypes.MemberType },
{ typeof(IMemberGroup).FullName!, UmbracoObjectTypes.MemberGroup },
{ typeof(ITemplate).FullName!, UmbracoObjectTypes.Template },
};
}
@@ -314,14 +316,18 @@ public class EntityService : RepositoryService, IEntityService
out long totalRecords,
IQuery<IUmbracoEntity>? filter = null,
Ordering? ordering = null)
{
using (ScopeProvider.CreateCoreScope(autoComplete: true))
{
IQuery<IUmbracoEntity> query = Query<IUmbracoEntity>().Where(x => x.ParentId == id && x.Trashed == false);
=> GetPagedChildren(id, objectType, pageIndex, pageSize, false, filter, ordering, out totalRecords);
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
}
}
/// <inheritdoc />
public IEnumerable<IEntitySlim> GetPagedTrashedChildren(
int id,
UmbracoObjectTypes objectType,
long pageIndex,
int pageSize,
out long totalRecords,
IQuery<IUmbracoEntity>? filter = null,
Ordering? ordering = null)
=> GetPagedChildren(id, objectType, pageIndex, pageSize, true, filter, ordering, out totalRecords);
/// <inheritdoc />
public IEnumerable<IEntitySlim> GetPagedDescendants(
@@ -523,4 +529,23 @@ public class EntityService : RepositoryService, IEntityService
return objType;
}
private IEnumerable<IEntitySlim> GetPagedChildren(
int id,
UmbracoObjectTypes objectType,
long pageIndex,
int pageSize,
bool trashed,
IQuery<IUmbracoEntity>? filter,
Ordering? ordering,
out long totalRecords)
{
using (ScopeProvider.CreateCoreScope(autoComplete: true))
{
IQuery<IUmbracoEntity> query = Query<IUmbracoEntity>().Where(x => x.ParentId == id && x.Trashed == trashed);
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
}
}
}

View File

@@ -186,6 +186,22 @@ public interface IEntityService
IQuery<IUmbracoEntity>? filter = null,
Ordering? ordering = null);
/// <summary>
/// Gets children of an entity.
/// </summary>
IEnumerable<IEntitySlim> GetPagedTrashedChildren(
int id,
UmbracoObjectTypes objectType,
long pageIndex,
int pageSize,
out long totalRecords,
IQuery<IUmbracoEntity>? filter = null,
Ordering? ordering = null)
{
totalRecords = 0;
return Array.Empty<IEntitySlim>();
}
/// <summary>
/// Gets descendants of an entity.
/// </summary>

View File

@@ -49,6 +49,15 @@ public interface ILocalizationService : IService
/// </returns>
IDictionaryItem? GetDictionaryItemById(Guid id);
/// <summary>
/// Gets a collection of <see cref="IDictionaryItem" /> by their <see cref="Guid" /> ids
/// </summary>
/// <param name="ids">Ids of the <see cref="IDictionaryItem" /></param>
/// <returns>
/// A collection of <see cref="IDictionaryItem" />
/// </returns>
IEnumerable<IDictionaryItem> GetDictionaryItemsByIds(params Guid[] ids) => Array.Empty<IDictionaryItem>();
/// <summary>
/// Gets a <see cref="IDictionaryItem" /> by its key
/// </summary>
@@ -58,6 +67,15 @@ public interface ILocalizationService : IService
/// </returns>
IDictionaryItem? GetDictionaryItemByKey(string key);
/// <summary>
/// Gets a collection of <see cref="IDictionaryItem" /> by their keys
/// </summary>
/// <param name="keys">Keys of the <see cref="IDictionaryItem" /></param>
/// <returns>
/// A collection of <see cref="IDictionaryItem" />
/// </returns>
IEnumerable<IDictionaryItem> GetDictionaryItemsByKeys(params string[] keys) => Array.Empty<IDictionaryItem>();
/// <summary>
/// Gets a list of children for a <see cref="IDictionaryItem" />
/// </summary>

View File

@@ -170,6 +170,29 @@ internal class LocalizationService : RepositoryService, ILocalizationService
}
}
/// <summary>
/// Gets a collection <see cref="IDictionaryItem" /> by their <see cref="Guid" /> ids
/// </summary>
/// <param name="ids">Ids of the <see cref="IDictionaryItem" /></param>
/// <returns>
/// A collection of <see cref="IDictionaryItem" />
/// </returns>
public IEnumerable<IDictionaryItem> GetDictionaryItemsByIds(params Guid[] ids)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
IEnumerable<IDictionaryItem> items = _dictionaryRepository.GetMany(ids).ToArray();
// ensure the lazy Language callback is assigned
foreach (IDictionaryItem item in items)
{
EnsureDictionaryItemLanguageCallback(item);
}
return items;
}
}
/// <summary>
/// Gets a <see cref="IDictionaryItem" /> by its key
/// </summary>
@@ -189,6 +212,28 @@ internal class LocalizationService : RepositoryService, ILocalizationService
}
}
/// <summary>
/// Gets a collection of <see cref="IDictionaryItem" /> by their keys
/// </summary>
/// <param name="keys">Keys of the <see cref="IDictionaryItem" /></param>
/// <returns>
/// A collection of <see cref="IDictionaryItem" />
/// </returns>
public IEnumerable<IDictionaryItem> GetDictionaryItemsByKeys(params string[] keys)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
IEnumerable<IDictionaryItem> items = _dictionaryRepository.GetManyByKeys(keys).ToArray();
// ensure the lazy Language callback is assigned
foreach (IDictionaryItem item in items)
{
EnsureDictionaryItemLanguageCallback(item);
}
return items;
}
}
/// <summary>
/// Gets a list of children for a <see cref="IDictionaryItem"/>
/// </summary>