[6.x] Select all entries, including all pages - #15283
Conversation
… count grew. Enter/exit slide still works.
Page through listing results instead of one oversized request, track all-matching mode explicitly, and cover cancel detection plus paging in tests.
Pass modelValue 'indeterminate' instead of a ignored indeterminate prop so the indicator mounts and shows the dash when listings are partially selected.
Hide the expand control when selections already cover the total, only reset all-matching mode on search/filter/sort changes, and use meta.total in header screen-reader copy.
|
Hell yea! |
jasonvarga
left a comment
There was a problem hiding this comment.
Nice work — the util/listing-selections.js extraction is well factored and genuinely well covered, and moving the header checkbox from global-selection semantics to page-scoped semantics is the right model. The Checkbox/Item.vue indeterminate fix is a real catch too.
A few things need sorting before this can land:
- The select-all state survives listing-scope changes.
matchingQueryKeydoesn't account forurloradditionalParameters, so navigating between asset folders (or switching user groups) leavesselectedAllMatchingset against a different result set. Details inline. - IDs aren't deduped when paging through results.
- There's no ceiling on how many items can be selected, and the resulting bulk action runs synchronously in a single request. This is the one I'd most like your thoughts on — see the inline comment.
The rest are smaller notes, including a translation regression worth avoiding.
|
|
||
| if (selectAllMatchingSource !== controller) return; | ||
|
|
||
| selections.value.splice(0, selections.value.length, ...ids); |
There was a problem hiding this comment.
There's no upper bound on how many IDs can end up here, and the downstream cost scales badly. On a 20k-entry collection:
perPageis clamped tomax(pagination_size_options)= 100 byStatamic::cpPerPage(), so this is ~200 sequential XHRs, each fully augmenting every visible column server-sidecomponents/actions/BulkActions.vue:44watchesselectionsdeeply and POSTs the whole array to/actions/liston change — 20k IDsActionController::run()then loads all 20k items, authorizes each one, and runs the action synchronously in a single request
That won't complete on most hosts, and the user just gets a spinner with no sense of the scale involved. Also worth noting that splice(0, len, ...ids) spreads the whole array as call arguments, which throws RangeError somewhere north of ~100k.
I think this needs either a configurable cap on the feature, or for bulk actions to grow a server-side "all matching" mode where the query is sent rather than the ID list. Happy to discuss which direction you'd prefer — the second is more work but is the version that actually holds up on a large site.
There was a problem hiding this comment.
I've added a configurable cap for this PR. AI suggested 1000 as a sensible number—but I understand this is not a satisfactory way to resolve things on a larger site. We could see what the response is? And plan a different PR to tackle that if needed.
…'s useful there and it causes complications Asset folders are counted in meta.total but aren't selectable, so the across-pages control under-selected. Opt out via allowSelectAllMatching.
matchingQueryKey ignored url and additionalParameters, so folder/group switches could leave selectedAllMatching set against a new result set.
Overlapping pages (e.g. if the result set shifts mid-fetch) could push the same id more than once into selections.
Cap "Select all matching" at config('statamic.cp.select_all_limit')
(default 1000) so sync bulk actions aren't invited on huge result sets.
Null disables the ceiling.
Comparing against meta.total cleared the flag (and resurrected the CTA) whenever search indexes or mid-fetch deletes returned fewer IDs. Track the fetched count instead and only exit all-matching when the user removes a selection.
Use the pagination size ceiling instead of the listing's current per-page, and ask only for the id column so each page skips full augmentation.
Reserving it only while the select-all row was visible made action shortcut letters shift mid-interaction.
These values are always booleans by the time they reach the toolbar, so the Object union and toValue() unwrapping were unnecessary.
The optional call was only added for the test mock, which already defines isCancel.
|
By the way, I've removed this from |
Description of the Problem
If you have many entries in an index, pagination occurs and it's not possible to select every single entry.
It's only possible to select the entries on the current page.
NB This PR was created in part, so that it would be possible to bulk-localize all entries in a collection, to go with the PR #15280
Before this PR
Even though there are 3 pages here, you can only effectively select page 1
What this PR Does
This PR adds select all matching results across pages on shared CP listings (entries, users, terms, assets, etc.).
Before: The header checkbox only selected the current page. You could accumulate IDs by paging, but there was no “select everything that matches this listing.”
After:
Select all rows on the current page.
If more results exist, the floating toolbar offers Select :total items across all pages (shortcut A).
That fetches every matching ID (paging through the listing API so it isn’t capped by cpPerPage) and selects them for bulk actions.
How to Reproduce
/cp/collections/blog