Skip to content

Content Types: Page through all allowed content types rather than stopping at the first 100 (closes #23512) - #23522

Open
AndyButland wants to merge 3 commits into
v17/devfrom
v17/bugfix/23512-allowed-children-pagination
Open

Content Types: Page through all allowed content types rather than stopping at the first 100 (closes #23512)#23522
AndyButland wants to merge 3 commits into
v17/devfrom
v17/bugfix/23512-allowed-children-pagination

Conversation

@AndyButland

@AndyButland AndyButland commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Description

A Document Type that allows more than 100 child Document Types only offered the first 100 in the Content section's Create dialog. Perhaps an edge case setup, but we should support retrieving all, similar to what we did recently for languages in #22765.

Fixes #23512

There were three separate issues to resolve.

1. The client never paged (the user-visible cause)

requestAllowedChildrenOf had no paging parameters anywhere in the chain — data source, interface or repository. It fired a single request with no skip/take, so the server applied its default take of 100, and every one of the 8 consumers read data.items while discarding the total the server correctly reported. Two of them carried a // TODO: implement pagination, or get 1000?.

Fixed by paging through the full set with the fetchAllPages helper added in #22765 for the >100 languages case. This is additive, mirroring that PR's requestCollection / requestAllItems split:

  • requestAllAllowedChildrenOf (new) pages until every item is retrieved — all in-repo callers now use it.
  • requestAllowedChildrenOf gains an optional paging argument, so a single page can be requested. Its existing behaviour is unchanged for external consumers.

The paging?: UmbOffsetPaginationRequestModel shape matches UmbTreeChildrenOfRequestArgs and the search args rather than reintroducing bare skip/take, which were deprecated in 16.3.

This affects allowed at root as much as allowed children, and needs resolving for every content type with an allowed-children/allowed-at-root endpoint — document, media and member types. Members need no configuration to hit it: every member type is a root candidate.

2. The server paged incorrectly (a latent prerequisite)

ContentTypeServiceBase.GetAllowedChildrenAsync applied .Take(take).Skip(skip) — inverted. At skip=0 that is harmless, which is why it went unnoticed; at skip=100, take=100 it returned zero items while Total still reported the true count. Client paging could not work until this was corrected.

3. Media-type siblings, same failure class

requestMediaTypesOfFolders and requestMediaTypesOf hardcoded take = 100 against paged endpoints. The dropzone and the media picker's folder path intersect those lists with allowed children, so fixing only the allowed-children side would still have dropped valid options.

Testing

Automated

Automated coverage added client and server; solution should build and CI checks pass.

Manual

The page size is a constant, so temporarily lowering it means we need 6 Document Types rather than 101.

Setup — temporarily set the page size to 5:

  1. In src/Umbraco.Web.UI.Client/src/packages/content/content-type/repository/structure/content-type-structure-repository-base.ts, change:
    const ALLOWED_CHILDREN_PAGE_SIZE = 100;   // →  5
  2. For the media steps, do the same in src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.repository.ts:
    const MEDIA_TYPE_PAGE_SIZE = 100;         // →  5
  3. Run the site and open the backoffice with the browser network tab filtered to allowed.

While the page size is lowered, four of the client unit tests fail (expected [ Array(30) ] to deeply equal [ Array(2) ]) — they assert two requests at the real page size of 100, and 150 mock items at a page size of 5 is 30 requests. Expected, and resolved by the teardown step.

Server-side check (needs no Document Types at all beyond a parent with 6 children). In Swagger, call GET /umbraco/management/api/v1/document-type/{id}/allowed-children?skip=5&take=5. It returns the remaining item(s) with the full total. On v17/dev today the same call returns an empty items array with a non-zero total — that is defect 2.

Allowed children:

  1. Create 6 Document Types, and a parent Document Type that allows all 6 as children.
  2. Create a page of the parent type, then choose Create beneath it.
  3. All 6 appear. The network tab shows two requests: skip=0&take=5 then skip=5&take=5.

Allowed at root:

  1. Tick Allow at root on all 6 Document Types.
  2. Choose Create on the Content tree root.
  3. All 6 appear, over two allowed-at-root requests.

Members (no configuration needed — every member type is a root candidate):

  1. Create 6 Member Types, then choose Create in the Members section. All 6 appear.

Media:

  1. Repeat steps 4–9 in the Media section with 6 Media Types.
  2. With 6 folder Media Types configured, open a Media Picker and confirm folders still render as folders (not as files) and the create folder options are all present.
  3. Drag a file into the Media section whose only valid Media Type sits past position 5 in the allowed list, and confirm the upload is accepted.

Collection views: with the parent type allowing 6 children, open its collection (list view) and confirm the Create dropdown offers all 6.

Teardown: revert both page-size constants to 100.

The Create dialogs, collection create actions, media dropzone and media
picker only ever requested one page of allowed content types, so anything
past the server's default take of 100 was silently unavailable.

Page through the full set with the existing fetchAllPages helper, via a new
additive requestAllAllowedChildrenOf. The existing requestAllowedChildrenOf
gains an optional paging argument so a single page can still be requested.

GetAllowedChildrenAsync applied Take before Skip, so any page after the
first came back empty while Total reported the true count. This never
surfaced while every caller requested only skip=0.

Also page requestMediaTypesOfFolders and requestMediaTypesOf, which the
dropzone and media picker intersect with allowed children, and which build
the folder-type set that decides whether an item renders as a folder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 10:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @AndyButland's task in 10m 5s —— View job


Code Review Complete

  • Read review skill instructions
  • Gather PR diff and changed files (26 files: 1 C#, 22 TS, 3 integration test)
  • Review C# backend changes
  • Review TypeScript frontend changes
  • Review test coverage
  • Post inline comments for findings
  • Post summary comment
  • Apply labels (area/frontend, area/backend)

Summary: No critical issues. One important gap (no TS test for UmbMediaTypeStructureRepository's conditional paging logic). Two suggestions (comment on intentional skip=0 boundary test; || vs ?? in mock utility). Overall verdict: Approved with Suggestions for improvement.

Comment thread src/Umbraco.Web.UI.Client/mocks/utils.ts Outdated
@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Review

Target: origin/v17/dev | Based on commit: 4cc8d53

Fixes two independent bugs causing the Create dialog to silently truncate at 100 allowed types: an inverted Skip/Take in ContentTypeServiceBase.GetAllowedChildrenAsync (zero items returned for any skip > 0), and client-side repository methods that never sent paging parameters. Both sides are corrected with automated tests.

Modified public API:

  • UmbContentTypeStructureRepository interface: requestAllowedChildrenOf widened; requestAllAllowedChildrenOf? added as optional
  • UmbContentTypeStructureDataSource: getAllowedChildrenOf gains optional paging
  • UmbContentTypeStructureRepositoryBase: new requestAllAllowedChildrenOf concrete method
  • UmbMediaTypeStructureRepository: requestMediaTypesOf/requestMediaTypesOfFolders now page through all results when no explicit skip/take is supplied

Affected implementations (outside this PR): Third-party classes implementing UmbContentTypeStructureRepository or UmbContentTypeStructureDataSource (both exported from @umbraco-cms/backoffice/content-type). Changes are additive so no compile-time break.

Other changes: MSW mock handlers now respect skip/take query params - any Storybook story relying on >100 items without explicit pagination will now only receive the first page.


Critical

None.

Important

media-type-structure.repository.ts:63 - No TypeScript test for the requestMediaTypesOf/requestMediaTypesOfFolders conditional paging logic. Both are publicly exported from @umbraco-cms/backoffice/media-type and their new fetch-all-vs-single-page branching is untested. document-type-structure.repository.test.ts covers only the base-class path. A test (no args two paged requests for 150 items; explicit skip/take one request) would give the same fail-before guarantee the C# tests have.

Suggestions

ContentTypeServiceTests.cs:2723 - Can_Get_First_Page_Of_Allowed_Children (skip=0) passes regardless of the bug since Take().Skip() and Skip().Take() are equivalent at skip=0. A short inline comment would preserve this rationale in the test file without requiring a PR description lookup.

mocks/utils.ts:67 - The || operator in Number(url.searchParams.get('skip')) || 0 treats numeric 0 as falsy, so the default applies by coincidence for skip=0. Null-coalescing before Number() communicates intent more clearly. Same for the take line.


Approved with Suggestions for improvement

Solid, well-scoped fix on both sides. The C# one-liner is exactly right, the TypeScript paging approach follows the established fetchAllPages pattern, and the test suite directly verifies the specific failure modes described. The only meaningful gap is TS-side coverage for UmbMediaTypeStructureRepository's conditional paging logic.

AndyButland and others added 2 commits July 31, 2026 12:13
The "explicit skip/take means one page, otherwise fetch all" branch in
requestMediaTypesOf and requestMediaTypesOfFolders was the novel logic in
this PR and had no test.

Also honour an explicit skip=0/take=0 in the mock paging helper rather than
relying on 0 being falsy, while still falling back on an unparseable value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The paged mock endpoints keep their paging in the db/manager layer and hand
skip/take down from the handler, with pagedResult doing the slice. The
allowed-children and allowed-at-root db methods now do the same, rather than
collecting everything and re-slicing it in the handler through a helper that
duplicated pagedResult.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@nikolajlauridsen nikolajlauridsen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend changes looks good.

Also did all the manual tests, and they all looked great 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants