Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a1340a7
Add flag support for pending changes and scheduled publish
lauraneto Feb 23, 2026
a2b925b
Fix HasScheduleFlagProvider test mocks to match refactored per-item l…
lauraneto Feb 23, 2026
3a1c23e
Extract PublishableVariantItemResponseModelBase to deduplicate varian…
lauraneto Feb 23, 2026
e9cbc6e
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Feb 24, 2026
2e91370
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Feb 25, 2026
500cccb
Extract shared base class from Document/Element presentation factories
lauraneto Mar 2, 2026
c2030d7
Fix flags fallback to use empty array instead of empty string
lauraneto Mar 3, 2026
8c6cb5e
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Mar 3, 2026
dc4ef1d
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Mar 4, 2026
3563320
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Mar 4, 2026
10e59d7
Acceptance Tests: Fix element tree item locator to match both element…
lauraneto Mar 4, 2026
c435927
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Mar 6, 2026
76c6f52
Merge branch 'v18/dev' into v18/feature/element-flag-support
AndyButland Mar 6, 2026
5c12f78
Merge branch 'v18/dev' into v18/feature/element-flag-support
nielslyngsoe Mar 10, 2026
54fbbd8
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Mar 26, 2026
d42f520
Split HasScheduleFlagProvider into document and element providers
lauraneto Mar 26, 2026
38a89cb
Make tree and recycle bin mapping methods async
lauraneto Mar 27, 2026
327b904
Extract Task.WhenAll select expressions into named variables
lauraneto Mar 27, 2026
f449d3a
Add missing XML docs to async methods on IDocumentPresentationFactory
lauraneto Mar 27, 2026
71ffacd
Fix DateTime vs DateTimeOffset comparison in schedule flag provider
lauraneto Mar 30, 2026
9dbafc3
Merge branch 'v18/dev' into v18/feature/element-flag-support
lauraneto Mar 30, 2026
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 @@ -60,24 +60,26 @@ protected override Ordering ItemOrdering
}
}

protected override DataTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentId, IEntitySlim[] entities)
protected override async Task<DataTypeTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentId, IEntitySlim[] entities)
{
Dictionary<int, IDataType> dataTypes = entities.Any()
? _dataTypeService
.GetAllAsync(entities.Select(entity => entity.Key).ToArray()).GetAwaiter().GetResult()
? (await _dataTypeService
.GetAllAsync(entities.Select(entity => entity.Key).ToArray()))
.ToDictionary(contentType => contentType.Id)
: new Dictionary<int, IDataType>();

return entities.Select(entity =>
IEnumerable<Task<DataTypeTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
DataTypeTreeItemResponseModel responseModel = MapTreeItemViewModel(parentId, entity);
DataTypeTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentId, entity);
if (dataTypes.TryGetValue(entity.Id, out IDataType? dataType))
{
responseModel.EditorUiAlias = dataType.EditorUiAlias;
responseModel.IsDeletable = dataType.IsDeletableDataType();
}

return responseModel;
}).ToArray();
});

return await Task.WhenAll(tasks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public async Task<IActionResult> Item(
.GetAll(UmbracoObjectTypes.Document, ids.ToArray())
.OfType<IDocumentEntitySlim>();

IEnumerable<DocumentItemResponseModel> responseModels = documents.Select(_documentPresentationFactory.CreateItemResponseModel);
IEnumerable<Task<DocumentItemResponseModel>> tasks = documents.Select(_documentPresentationFactory.CreateItemResponseModelAsync);
DocumentItemResponseModel[] responseModels = await Task.WhenAll(tasks);
return Ok(responseModels);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ public async Task<IActionResult> SearchWithTrashed(
take,
ignoreUserStartNodes);

IEnumerable<Task<DocumentItemResponseModel>> tasks = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModelAsync);
DocumentItemResponseModel[] items = await Task.WhenAll(tasks);

var result = new PagedModel<DocumentItemResponseModel>
{
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
Items = items,
Total = searchResult.Total,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ public DocumentRecycleBinControllerBase(IEntityService entityService, IDocumentP

protected override Guid RecycleBinRootKey => Constants.System.RecycleBinContentKey;

protected override DocumentRecycleBinItemResponseModel MapRecycleBinViewModel(Guid? parentId, IEntitySlim entity)
protected override async Task<DocumentRecycleBinItemResponseModel> MapRecycleBinViewModelAsync(Guid? parentId, IEntitySlim entity)
{
DocumentRecycleBinItemResponseModel responseModel = base.MapRecycleBinViewModel(parentId, entity);
DocumentRecycleBinItemResponseModel responseModel = await base.MapRecycleBinViewModelAsync(parentId, entity);

if (entity is IDocumentEntitySlim documentEntitySlim)
{
responseModel.Variants = _documentPresentationFactory.CreateVariantsItemResponseModels(documentEntitySlim);
responseModel.Variants = await _documentPresentationFactory.CreateVariantsItemResponseModelsAsync(documentEntitySlim);
responseModel.DocumentType = _documentPresentationFactory.CreateDocumentTypeReferenceResponseModel(documentEntitySlim);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ protected DocumentTreeControllerBase(

protected override Ordering ItemOrdering => Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.SortOrderColumnName);

protected override DocumentTreeItemResponseModel MapTreeItemViewModel(Guid? parentId, IEntitySlim entity)
protected override async Task<DocumentTreeItemResponseModel> MapTreeItemViewModelAsync(Guid? parentId, IEntitySlim entity)
{
DocumentTreeItemResponseModel responseModel = base.MapTreeItemViewModel(parentId, entity);
DocumentTreeItemResponseModel responseModel = await base.MapTreeItemViewModelAsync(parentId, entity);

if (entity is IDocumentEntitySlim documentEntitySlim)
{
Expand All @@ -94,7 +94,7 @@ protected override DocumentTreeItemResponseModel MapTreeItemViewModel(Guid? pare
responseModel.Id = entity.Key;
responseModel.CreateDate = entity.CreateDate;

responseModel.Variants = _documentPresentationFactory.CreateVariantsItemResponseModels(documentEntitySlim);
responseModel.Variants = await _documentPresentationFactory.CreateVariantsItemResponseModelsAsync(documentEntitySlim);
responseModel.DocumentType = _documentPresentationFactory.CreateDocumentTypeReferenceResponseModel(documentEntitySlim);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,19 @@ protected override Ordering ItemOrdering
}
}

protected override DocumentBlueprintTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentId, IEntitySlim[] entities)
=> entities.Select(entity =>
protected override async Task<DocumentBlueprintTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentId, IEntitySlim[] entities)
{
IEnumerable<Task<DocumentBlueprintTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
DocumentBlueprintTreeItemResponseModel responseModel = MapTreeItemViewModel(parentId, entity);
DocumentBlueprintTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentId, entity);
if (entity is IDocumentEntitySlim documentEntitySlim)
{
responseModel.HasChildren = false;
responseModel.DocumentType = _documentPresentationFactory.CreateDocumentTypeReferenceResponseModel(documentEntitySlim);
}
return responseModel;
}).ToArray();
});

return await Task.WhenAll(tasks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,24 @@ public DocumentTypeTreeControllerBase(

protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.DocumentTypeContainer;

protected override DocumentTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
protected override async Task<DocumentTypeTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentKey, IEntitySlim[] entities)
{
var contentTypes = _contentTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);

return entities.Select(entity =>
IEnumerable<Task<DocumentTypeTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
DocumentTypeTreeItemResponseModel responseModel = MapTreeItemViewModel(parentKey, entity);
DocumentTypeTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentKey, entity);
if (contentTypes.TryGetValue(entity.Id, out IContentType? contentType))
{
responseModel.Icon = contentType.Icon ?? responseModel.Icon;
responseModel.IsElement = contentType.IsElement;
}

return responseModel;
}).ToArray();
});

return await Task.WhenAll(tasks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@ public ItemElementItemController(
[ProducesResponseType(typeof(IEnumerable<ElementItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of element items.")]
[EndpointDescription("Gets a collection of element items identified by the provided Ids.")]
public Task<IActionResult> Item(
public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<ElementItemResponseModel>()));
return Ok(Enumerable.Empty<ElementItemResponseModel>());
}

IEnumerable<IElementEntitySlim> elements = _entityService
.GetAll(UmbracoObjectTypes.Element, ids.ToArray())
.OfType<IElementEntitySlim>();

IEnumerable<ElementItemResponseModel> responseModels = elements.Select(_elementPresentationFactory.CreateItemResponseModel);
return Task.FromResult<IActionResult>(Ok(responseModels));
IEnumerable<Task<ElementItemResponseModel>> tasks = elements.Select(_elementPresentationFactory.CreateItemResponseModelAsync);
ElementItemResponseModel[] responseModels = await Task.WhenAll(tasks);
return Ok(responseModels);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ public ElementRecycleBinControllerBase( IEntityService entityService, IElementPr

protected override Guid RecycleBinRootKey => Constants.System.RecycleBinElementKey;

protected override ElementRecycleBinItemResponseModel MapRecycleBinViewModel(Guid? parentId, IEntitySlim entity)
protected override async Task<ElementRecycleBinItemResponseModel> MapRecycleBinViewModelAsync(Guid? parentId, IEntitySlim entity)
{
ElementRecycleBinItemResponseModel responseModel = base.MapRecycleBinViewModel(parentId, entity);
ElementRecycleBinItemResponseModel responseModel = await base.MapRecycleBinViewModelAsync(parentId, entity);

responseModel.Name = entity.Name ?? string.Empty;

if (entity is IElementEntitySlim elementEntitySlim)
{
responseModel.Variants = _elementPresentationFactory.CreateVariantsItemResponseModels(elementEntitySlim);
responseModel.Variants = await _elementPresentationFactory.CreateVariantsItemResponseModelsAsync(elementEntitySlim);
responseModel.DocumentType = _elementPresentationFactory.CreateDocumentTypeReferenceResponseModel(elementEntitySlim);
responseModel.IsFolder = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,23 @@ protected override string[] GetUserStartNodePaths()
.GetElementStartNodePaths(EntityService, _appCaches)
?? [];

protected override ElementTreeItemResponseModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
protected override async Task<ElementTreeItemResponseModel> MapTreeItemViewModelAsync(Guid? parentKey, IEntitySlim entity)
{
ElementTreeItemResponseModel responseModel = base.MapTreeItemViewModel(parentKey, entity);
ElementTreeItemResponseModel responseModel = await base.MapTreeItemViewModelAsync(parentKey, entity);

if (entity is IElementEntitySlim elementEntitySlim)
{
responseModel.CreateDate = elementEntitySlim.CreateDate;
responseModel.DocumentType = _elementPresentationFactory.CreateDocumentTypeReferenceResponseModel(elementEntitySlim);
responseModel.Variants = _elementPresentationFactory.CreateVariantsItemResponseModels(elementEntitySlim);
responseModel.Variants = await _elementPresentationFactory.CreateVariantsItemResponseModelsAsync(elementEntitySlim);
}

return responseModel;
}

protected override ElementTreeItemResponseModel MapTreeItemViewModelAsNoAccess(Guid? parentKey, IEntitySlim entity)
protected override async Task<ElementTreeItemResponseModel> MapTreeItemViewModelAsNoAccessAsync(Guid? parentKey, IEntitySlim entity)
{
ElementTreeItemResponseModel viewModel = MapTreeItemViewModel(parentKey, entity);
ElementTreeItemResponseModel viewModel = await MapTreeItemViewModelAsync(parentKey, entity);
viewModel.NoAccess = true;
return viewModel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public MediaRecycleBinControllerBase(IEntityService entityService, IMediaPresent

protected override Guid RecycleBinRootKey => Constants.System.RecycleBinMediaKey;

protected override MediaRecycleBinItemResponseModel MapRecycleBinViewModel(Guid? parentKey, IEntitySlim entity)
protected override async Task<MediaRecycleBinItemResponseModel> MapRecycleBinViewModelAsync(Guid? parentKey, IEntitySlim entity)
{
MediaRecycleBinItemResponseModel responseModel = base.MapRecycleBinViewModel(parentKey, entity);
MediaRecycleBinItemResponseModel responseModel = await base.MapRecycleBinViewModelAsync(parentKey, entity);

if (entity is IMediaEntitySlim mediaEntitySlim)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ public MediaTreeControllerBase(

protected override Ordering ItemOrdering => Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.SortOrderColumnName);

protected override MediaTreeItemResponseModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
protected override async Task<MediaTreeItemResponseModel> MapTreeItemViewModelAsync(Guid? parentKey, IEntitySlim entity)
{
MediaTreeItemResponseModel responseModel = base.MapTreeItemViewModel(parentKey, entity);
MediaTreeItemResponseModel responseModel = await base.MapTreeItemViewModelAsync(parentKey, entity);

if (entity is IMediaEntitySlim mediaEntitySlim)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,24 @@ public MediaTypeTreeControllerBase(

protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.MediaTypeContainer;

protected override MediaTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
protected override async Task<MediaTypeTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentKey, IEntitySlim[] entities)
{
var mediaTypes = _mediaTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);

return entities.Select(entity =>
IEnumerable<Task<MediaTypeTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
MediaTypeTreeItemResponseModel responseModel = MapTreeItemViewModel(parentKey, entity);
MediaTypeTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentKey, entity);
if (mediaTypes.TryGetValue(entity.Id, out IMediaType? mediaType))
{
responseModel.Icon = mediaType.Icon ?? responseModel.Icon;
responseModel.IsDeletable = mediaType.IsSystemMediaType() is false;
}

return responseModel;
}).ToArray();
});

return await Task.WhenAll(tasks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,23 @@ public MemberTypeTreeControllerBase(

protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.MemberTypeContainer;

protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
protected override async Task<MemberTypeTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentKey, IEntitySlim[] entities)
{
var memberTypes = _memberTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);

return entities.Select(entity =>
IEnumerable<Task<MemberTypeTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
MemberTypeTreeItemResponseModel responseModel = MapTreeItemViewModel(parentKey, entity);
MemberTypeTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentKey, entity);
if (memberTypes.TryGetValue(entity.Id, out IMemberType? memberType))
{
responseModel.Icon = memberType.Icon ?? responseModel.Icon;
}

return responseModel;
}).ToArray();
});

return await Task.WhenAll(tasks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,26 @@ protected RecycleBinControllerBase(IEntityService entityService)

protected abstract Guid RecycleBinRootKey { get; }

protected Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take)
protected async Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take)
{
IEntitySlim[] rootEntities = GetPagedRootEntities(skip, take, out var totalItems);

TItem[] treeItemViewModels = MapRecycleBinViewModels(null, rootEntities);
TItem[] treeItemViewModels = await MapRecycleBinViewModelsAsync(null, rootEntities);

PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems);

return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result));
return Ok(result);
}

protected Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentKey, int skip, int take)
protected async Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentKey, int skip, int take)
{
IEntitySlim[] children = GetPagedChildEntities(parentKey, skip, take, out var totalItems);

TItem[] treeItemViewModels = MapRecycleBinViewModels(parentKey, children);
TItem[] treeItemViewModels = await MapRecycleBinViewModelsAsync(parentKey, children);

PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems);

return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result));
return Ok(result);
}

protected async Task<ActionResult<SubsetViewModel<TItem>>> GetSiblings(Guid target, int before, int after)
Expand All @@ -61,14 +61,14 @@ protected async Task<ActionResult<SubsetViewModel<TItem>>> GetSiblings(Guid targ
IEntitySlim entity = siblings.First();
Guid? parentKey = GetParentKey(entity);

TItem[] treeItemViewModels = MapRecycleBinViewModels(parentKey, siblings);
TItem[] treeItemViewModels = await MapRecycleBinViewModelsAsync(parentKey, siblings);

SubsetViewModel<TItem> result = SubsetViewModel(treeItemViewModels, totalBefore, totalAfter);

return Ok(result);
}

protected virtual TItem MapRecycleBinViewModel(Guid? parentKey, IEntitySlim entity)
protected virtual Task<TItem> MapRecycleBinViewModelAsync(Guid? parentKey, IEntitySlim entity)
{
if (entity == null)
{
Expand All @@ -88,7 +88,7 @@ protected virtual TItem MapRecycleBinViewModel(Guid? parentKey, IEntitySlim enti
: null
};

return viewModel;
return Task.FromResult(viewModel);
}

protected IActionResult OperationStatusResult(OperationResult result) =>
Expand Down Expand Up @@ -154,8 +154,11 @@ protected virtual IEntitySlim[] GetPagedChildEntities(Guid parentKey, int skip,
return children;
}

private TItem[] MapRecycleBinViewModels(Guid? parentKey, IEntitySlim[] entities)
=> entities.Select(entity => MapRecycleBinViewModel(parentKey, entity)).ToArray();
private async Task<TItem[]> MapRecycleBinViewModelsAsync(Guid? parentKey, IEntitySlim[] entities)
{
IEnumerable<Task<TItem>> tasks = entities.Select(entity => MapRecycleBinViewModelAsync(parentKey, entity));
return await Task.WhenAll(tasks);
}

private PagedViewModel<TItem> PagedViewModel(IEnumerable<TItem> treeItemViewModels, long totalItems)
=> new() { Total = totalItems, Items = treeItemViewModels };
Expand Down
Loading
Loading