Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core Data: Implement _fields data reuse for entities #19498

Merged
merged 5 commits into from
Aug 24, 2020

Conversation

aduth
Copy link
Member

@aduth aduth commented Jan 7, 2020

Closes #15114
Related: #18988, #13618, #18586

This pull request seeks to implement _fields filtering and data reuse for core data entities. With these changes, the getEntityRecord and getEntityRecords selectors will behave in such a way that _fields queries will only trigger a request to the REST API if there was not already a previous request for either (a) the complete entity record or (b) another _fields filtered request which satisfies (is a superset of) the currently-requested fields.

These changes would enable a number of potential optimizations, where currently we do not implement _fields querying, even when only using a limited subset of the available data. This is due to a previous lack of support of this functionality. One major improvement this may enable is to omit the post's content.rendered value from the bootstrapped post data, which is unused, potentially substantial, and requires server-side processing (the_content filtering) to generate. See also #18988.

Testing Instructions:

There should be no expected impact on the runtime of the editor application, since the included changes implement support for _fields filtering, but it is not yet leveraged anywhere.

Verify that there is no change in the expected behavior of common entities use (e.g. post loading, edits, etc).

Ensure unit tests pass:

npm run test-unit

Testing the added support is most effective using your browser's DevTools console and, optionally, the Redux DevTools browser extension (to verify expected state shape, resolution behaviors).

Example:

wp.data.select('core').getEntityRecord( 'postType', 'post', 1, { _fields: [ 'author', 'content' ] } )
// null

wp.data.select('core').getEntityRecord( 'postType', 'post', 1, { _fields: [ 'author', 'content' ] } )
// {author: 1, content: {…}}

wp.data.select('core').getEntityRecord( 'postType', 'post', 1, { _fields: [ 'author' ] } )
// {author: 1}

wp.data.select('core').getEntityRecord( 'postType', 'post', 1, { _fields: [ 'author', 'title' ] } )
// null

wp.data.select('core').getEntityRecord( 'postType', 'post', 1, { _fields: [ 'author', 'title' ] } )
// {author: 1, title: {…}}

wp.data.select('core').getEntityRecord( 'postType', 'post', 1, { _fields: [ 'author', 'title', 'content' ] } )
// {author: 1, title: {…}, content: {…}}

In this example, the null return values indicate where data is not yet available and a network request would have been issued. The subsequent identical selector call represents the data as it is expected to have been returned by the REST API. You can see in the later examples that, while there was no original network request for the aggregate of all of the fields, the available data is such that it can be returned immediately, without needings its own network call.

@aduth aduth added [Type] Performance Related to performance efforts REST API Interaction Related to REST API [Package] Core data /packages/core-data labels Jan 7, 2020
@aduth aduth added the [Status] In Progress Tracking issues with work in progress label Jan 7, 2020
@youknowriad
Copy link
Contributor

Cool PR ❤️ I haven't had the chance to look at the PR deeply but one thought here is: if we can have a mapping "context => fields" in the data package, we could also potentially trigger the "view" context request instead of "edit" if the fields requested are available there. This would solve issues we have where we can't use the data module in some components/blocks due to failures for the contributor role.

@aduth
Copy link
Member Author

aduth commented Jan 9, 2020

@youknowriad That's a good thought. I'm not sure it's something to be done here, though I've been thinking of a few things as well, where each could potentially have some impact on what an implementation should look like.

For example:

  • When multiple entity queries happen in the same tick (e.g. many components selecting an entity in a single render), how can we aggregate these such that only a single network request is issued for any one particular entity (i.e. a union of the requested _fields)
  • How do we deal with outliers (those which don't specify _fields)? In this model, while using _fields affords us an optimization, it relies on every reference to this entity to opt-in for it to have any effect. If, at any point, a selector is issued for an entity which does not also include a _fields query, then a network request must be issued. In many ways, this could be worse than if we did not optimize at all. That being said, since we at least have full control over the Gutenberg codebase, we could try to make these assurances in at least the first-party code.

@aduth
Copy link
Member Author

aduth commented May 25, 2020

I still think this is very valuable work, though given the current conflicts, it may need a bigger refresh than would be reasonable for a rebase. Similarly, there are some considerations mentioned in the prior two comments which might alter what's considered as an ideal implementation.

Going to close this for now. The issue #15114 remains open for tracking the task.

@aduth aduth closed this May 25, 2020
@aduth aduth deleted the update/core-data-fields-query branch May 25, 2020 20:16
@youknowriad youknowriad restored the update/core-data-fields-query branch July 29, 2020 10:01
@youknowriad youknowriad reopened this Jul 29, 2020
@youknowriad
Copy link
Contributor

I've restored this branch, I'd like to see how much effort it would take to refresh it.

@youknowriad youknowriad force-pushed the update/core-data-fields-query branch from 40ec2f2 to ce01c26 Compare July 29, 2020 10:25
@youknowriad youknowriad self-assigned this Jul 29, 2020
@github-actions
Copy link

github-actions bot commented Jul 29, 2020

Size Change: -38 B (0%)

Total Size: 1.16 MB

Filename Size Change
build/block-editor/index.js 125 kB -54 B (0%)
build/block-library/editor-rtl.css 7.58 kB -53 B (0%)
build/block-library/editor.css 7.58 kB -53 B (0%)
build/block-library/index.js 132 kB -260 B (0%)
build/block-library/style-rtl.css 7.76 kB -65 B (0%)
build/block-library/style.css 7.77 kB -66 B (0%)
build/blocks/index.js 48.3 kB +28 B (0%)
build/components/index.js 200 kB +30 B (0%)
build/core-data/index.js 12.3 kB +475 B (3%)
build/editor/index.js 45.3 kB -20 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.67 kB 0 B
build/api-fetch/index.js 3.44 kB 0 B
build/autop/index.js 2.82 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/index.js 7.93 kB 0 B
build/block-directory/style-rtl.css 953 B 0 B
build/block-directory/style.css 952 B 0 B
build/block-editor/style-rtl.css 10.8 kB 0 B
build/block-editor/style.css 10.8 kB 0 B
build/block-library/theme-rtl.css 728 B 0 B
build/block-library/theme.css 729 B 0 B
build/block-serialization-default-parser/index.js 1.88 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/components/style-rtl.css 15.7 kB 0 B
build/components/style.css 15.7 kB 0 B
build/compose/index.js 9.68 kB 0 B
build/data-controls/index.js 1.29 kB 0 B
build/data/index.js 8.45 kB 0 B
build/date/index.js 5.38 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 3.23 kB 0 B
build/edit-navigation/index.js 10.9 kB 0 B
build/edit-navigation/style-rtl.css 1.08 kB 0 B
build/edit-navigation/style.css 1.08 kB 0 B
build/edit-post/index.js 304 kB 0 B
build/edit-post/style-rtl.css 5.61 kB 0 B
build/edit-post/style.css 5.61 kB 0 B
build/edit-site/index.js 17 kB 0 B
build/edit-site/style-rtl.css 3.06 kB 0 B
build/edit-site/style.css 3.06 kB 0 B
build/edit-widgets/index.js 9.38 kB 0 B
build/edit-widgets/style-rtl.css 2.45 kB 0 B
build/edit-widgets/style.css 2.45 kB 0 B
build/editor/editor-styles-rtl.css 537 B 0 B
build/editor/editor-styles.css 539 B 0 B
build/editor/style-rtl.css 3.8 kB 0 B
build/editor/style.css 3.79 kB 0 B
build/element/index.js 4.65 kB 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/index.js 7.72 kB 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/hooks/index.js 2.13 kB 0 B
build/html-entities/index.js 621 B 0 B
build/i18n/index.js 3.56 kB 0 B
build/is-shallow-equal/index.js 711 B 0 B
build/keyboard-shortcuts/index.js 2.52 kB 0 B
build/keycodes/index.js 1.94 kB 0 B
build/list-reusable-blocks/index.js 3.11 kB 0 B
build/list-reusable-blocks/style-rtl.css 476 B 0 B
build/list-reusable-blocks/style.css 476 B 0 B
build/media-utils/index.js 5.33 kB 0 B
build/notices/index.js 1.79 kB 0 B
build/nux/index.js 3.4 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/plugins/index.js 2.56 kB 0 B
build/primitives/index.js 1.41 kB 0 B
build/priority-queue/index.js 789 B 0 B
build/redux-routine/index.js 2.85 kB 0 B
build/rich-text/index.js 13.9 kB 0 B
build/server-side-render/index.js 2.71 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 4.06 kB 0 B
build/viewport/index.js 1.85 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.17 kB 0 B

compressed-size-action

@youknowriad youknowriad added Needs Dev Note Requires a developer note for a major WordPress release cycle and removed [Status] In Progress Tracking issues with work in progress labels Jul 29, 2020
Copy link
Contributor

@mcsf mcsf left a comment

Choose a reason for hiding this comment

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

This is awesome. I dropped some questions. Let's see how soon we can get this in. :)

*/
function getNormalizedCommaSeparable( value ) {
if ( typeof value === 'string' ) {
return value.split( ',' );
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor question: should we .map( trim ) in case the input looks like 'this, list, with, spaces'?

Copy link
Contributor

@youknowriad youknowriad Jul 30, 2020

Choose a reason for hiding this comment

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

I think it depends on whether the REST API supports spaces there or not. (_fields and include query args)

packages/core-data/src/test/selectors.js Outdated Show resolved Hide resolved
Comment on lines 117 to 120
// Queried data state is prepopulated for all known entities. If this is not
// assigned for the given parameters, then it is known to not exist. Thus, a
// return value of `undefined` is used instead of `null` (where `null` is
// otherwise used to represent an unknown state).
Copy link
Contributor

Choose a reason for hiding this comment

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

This sounds like implementation details (in particular, the way that state.entities.data is structured) bleeding into the interface of getEntityRecord, no?

I worry that this reversal of null and undefined is so unconventional that it would create confusion. Have you considered false for "known not to exist"? Or (re-)reversing null and undefined?

Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed it supports undefined is better for data that is not fetched yet and null for things we know don't exist. Why can't we switch it?

Copy link
Contributor

@mcsf mcsf Jul 30, 2020

Choose a reason for hiding this comment

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

I'm not sure I understood. :) Are you suggesting we change to what I said (undef -> unknown, null -> known nonexistence)? That would be good with me.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's my suggestion. At least it seems more logical to me but I don't know why @aduth went the other direction initially.

Copy link
Contributor

@youknowriad youknowriad Jul 30, 2020

Choose a reason for hiding this comment

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

Ok, I spent some time looking into this and I think we were misunderstanding the intent.

Returning undefined here doesn't mean the request record is known to not exist but it instead means the Entity doesn't exist (for example if I request a CPT "book" with id 1, it will return undefined if the entity doesn't exist but if the entity exists and the book is not found or not fetched yet, it will still return null)

I also found that we already return null in some of these selectors when requests are in progress or incomplete. So my suggestion would be to avoid the distinction between undefined and null here and always return null.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, I see then. In that case it makes to converge on null. 👍 We can always rely on other selectors to inquire on the status of data satisfaction and such.

Copy link
Member Author

Choose a reason for hiding this comment

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

I could have sworn that I'd been of the like opinion here that undefined is a good representation of "not yet known" and null of "known to be nothing". I'm not really sure why I'd reversed it here. I'd be agreeable to changing it though.

In general, I'd still agree it's a bit of an abuse of these values to try to signal the tri-state "unknown", "known and nothing", "known and something" via undefined, null, and some truthy value. I'm not sure the language gives us a great alternative here. I think there'd been cases where using some other value like false could be an issue (imagine something like wasSaveSuccessful, where false should mean "the save was not successful" and not "the save is still in progress and the status is not yet known"). Pretty sure there are real cases of this in the current code.

itemIsComplete: {
1: true,
2: true,
},
queries: [ 1, 2 ],
Copy link
Contributor

Choose a reason for hiding this comment

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

Not in the diff, but when would queries be an array?

@youknowriad
Copy link
Contributor

@mcsf @adamziel What's missing here? Can we land it?

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

No objections, let's try this.

@youknowriad youknowriad merged commit bfbcc92 into master Aug 24, 2020
@youknowriad youknowriad deleted the update/core-data-fields-query branch August 24, 2020 08:41
@github-actions github-actions bot added this to the Gutenberg 8.9 milestone Aug 24, 2020
@cguntur cguntur mentioned this pull request Oct 14, 2020
17 tasks
@tellthemachines tellthemachines removed the Needs Dev Note Requires a developer note for a major WordPress release cycle label Nov 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Core data /packages/core-data REST API Interaction Related to REST API [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Data: Consider optimizing aggregate of _fields requests
4 participants