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:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user