Core Data: Fix incorrect pagination for non-paginated entities#76406
Core Data: Fix incorrect pagination for non-paginated entities#76406
Conversation
|
Size Change: +54 B (0%) Total Size: 7.73 MB
ℹ️ View Unchanged
|
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @lmartins, @jjlmoya. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
packages/core-data/src/resolvers.js
Outdated
| query.per_page === undefined | ||
| ) { | ||
| query = { ...query, per_page: -1 }; | ||
| } |
There was a problem hiding this comment.
We should have done this a long time ago that said, it can be seen as a breaking change.
I know at some point @ellatrix wanted to use this loophole/bug to "auto-migrate" templates to the new "activate template" mode. I wonder if it's still the case or not.
There was a problem hiding this comment.
I tried to limit the breaking change effects. If perPage is defined it will be respected when slicing the records.
I don’t have info about auto-migration plan, but using similar bugs as loopholes doesn’t seem reliable.
There was a problem hiding this comment.
Why don't we check supportsPagination before slicing? Tbh, I've always thought per_page: -1 is wrong and we should use that property to define "unbound" requests. Even if you use per_page: -1 on pages that support pagination, it can be valuable to be able to set by how many pages it batches requests. per_page: -1 suggests it grabs all items in a single request, and that's simply not true.
There was a problem hiding this comment.
I think this is the closest we can do before slicing, right before action is dispatched to the reducer.
Tbh, I've always thought per_page: -1 is wrong and we should use that property to define "unbound" requests.
I think this was just copied from WP_Query, but I agree that, depending on the data, it might take more than a couple of requests. We could try to improve this, but I don't think it's in the scope of this PR.
There was a problem hiding this comment.
This is how I understand the problem, correct me if I'm wrong:
- the
/wp/v2/iconsendpoint returns an array of 80+ icons, with no pagination: ignores theper_pageandpagequery args, there are nox-wp-totalandx-wp-totalpagesheaders in the response. - the
getEntityRecords( 'root', 'icon' )selector will, however, return only first 10 items, becausegetQueriedItemsandgetQueryPartsassigns default values topageandper_page. Although the REST endpoint doesn't support pagination, it's implemented client-side, inside thegetEntityRecordsselector. - this PR partially disables this "client-side pagination" for entities that don't have
config.supportsPagination === true. It will work only when you specifypageandper_pageexplicitly. If you don't specify them,getEntityRecords( 'root', 'icon' )will return the full list by default.
it can be seen as a breaking change.
The breaking change is that the client-side pagination is no longer the default? I.e., that the getEntityRecords( 'root', 'icon' ) doesn't return just the first 10 items, but all 80+? Or is there also something else?
Implementation-wise, I don't like the fact that getQueriedItems always looks at page and per_page and respects them, and that we need getNonPaginatedQuery to create a "fake" paginated query, adding an artificial per_page=-1 query arg. I would prefer the following, if possible:
getQueryPartsdoesn't add any default values forpageandper_page. They are relevant only whensupportsPaginationis true.- it's the
getQueriedItemsselector that reads thesupportsPaginationflag and does the pagination accordingly, notgetNonPaginatedQuery.
There was a problem hiding this comment.
The breaking change is that the client-side pagination is no longer the default? I.e., that the getEntityRecords( 'root', 'icon' ) doesn't return just the first 10 items, but all 80+? Or is there also something else?
That could be considered a breaking change, but consumers were relying on the existing bug. The whole data set was received but then discarded.
@jsnajdr, I've pushed changes based on your feedback, plus rebased the branch after the offset fixes.
056fef3 to
54fb6f2
Compare
|
I've rebased and updated the Icons block query; if anyone wants to do manual testing. We can handle the remaining cases before merging. |
|
Flaky tests detected in 01bd86a. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/23886992053
|
7e1e8af to
0019cd7
Compare
| */ | ||
| function getQueriedItemsUncached( state, query ) { | ||
| function getQueriedItemsUncached( state, query, options = {} ) { | ||
| const { supportsPagination = false } = options; |
There was a problem hiding this comment.
Based on 849901f and to limit breaking changes, we might want to configure entities supportsPagination explicitly. Currently, entities don't support pagination by default.
Plan:
- Update core entity configs and explicitly define
supportsPagination. - If
supportsPaginationis undefined, selectors should work as before - slice the data on the client.
Update: This seems like the right direction. Pushed changes with the last commit.
| const queriedItems = queriedItemsCache.get( query ); | ||
| if ( queriedItems !== undefined ) { | ||
| return queriedItems; | ||
| export const getQueriedItems = createSelector( |
There was a problem hiding this comment.
Any idea why we have createSelector here when there is no dependents array to memoize by? The caching is implemented manually inside the selector.
There was a problem hiding this comment.
There was a problem hiding this comment.
Hm, I don't remember exactly. It does seem intentional and was documented in the function's DocBlock ("caches result by both per state (by reference) and per query (by deep equality)". It does practically have dependents on the whole state object. But in practice you're right that the inner caching logic is pretty effective on its own. I think the one key difference is that createSelector memoization is going to be a lot faster than EquivalentKeyMap lookup if the query object is a stable reference between calls.
There was a problem hiding this comment.
I think the one key difference is that createSelector memoization is going to be a lot faster than EquivalentKeyMap lookup if the query object is a stable reference between calls.
I think most consumers pass inline query objects into useSelect, which makes it harder to ensure stable references when you're dealing with dynamic query values (search, order, include, etc.). So actual benefit of this would be hard to measure.
| queriedItemsCache.set( query, items ); | ||
| return items; | ||
| } ); | ||
| const items = getQueriedItemsUncached( state, query, options ); |
There was a problem hiding this comment.
The options object doesn't need to be part of the cache key, is that right? It's always a constant for a given kind/name entity type combination.
jsnajdr
left a comment
There was a problem hiding this comment.
I have some doubts about the createSelector call, but it's preexisting and not a blocker. I'd like to have a closer look sometime later. Let's ship this 🚢
e7466e4 to
01bd86a
Compare
|
Thanks for the feedback and reviews, everyone! I added the "Needs Dev Note" label. Let's mention this behavior change in Misc dev notes. |
What?
Closes #15413.
PR fixes a bug where records returned by the REST API for entities that don't support pagination are limited to the default
per_page: 10. Consumers and Core code had to useper_page: -1"hack" to fix this behavior.Why?
Non-paginated entities like
postTypeandtaxonomyreturn all records in a single response, butgetEntityRecordswas slicing results to the default page size. This normalizes the query toper_page: -1for non-paginated entities in both the resolver and selectors, ensuring all records are returned.Todo
per_page: -1"hack".Testing Instructions
Testing Instructions for Keyboard
Same.
AI usage
I had the idea, used Claude for validation, improving comments and rubber ducking (is this still a thing?).