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

Navigation: preload more API requests #35402

Merged

Conversation

anton-vlasenko
Copy link
Contributor

@anton-vlasenko anton-vlasenko commented Oct 6, 2021

Description

When loading the Navigation screen, there are a lot of loading spinners due to API requests to /menus and /menu-items. We should preload these requests so that the screen appears faster.
The goal of this PR is to preload these requests.
Also, this PR fixes 2 small bugs in the core-data and api-fetch modules.
Fixes #24642

How has this been tested?

  1. Open the navigation page (wp-admin/admin.php?page=gutenberg-navigation).
  2. Make sure there is only 1 fetch/XHR request (see the screenshot below).

Screenshots

Before:
Screenshot 2021-10-06 at 16 44 50
After:
Screenshot 2021-10-06 at 17 29 11

Types of changes

New feature

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).

@github-actions
Copy link

github-actions bot commented Oct 6, 2021

Size Change: -4.04 kB (0%)

Total Size: 1.07 MB

Filename Size Change
build/block-editor/index.min.js 134 kB +44 B (0%)
build/block-editor/style-rtl.css 13.9 kB +2 B (0%)
build/block-editor/style.css 13.9 kB -5 B (0%)
build/block-library/blocks/button/editor-rtl.css 470 B -4 B (-1%)
build/block-library/blocks/button/editor.css 470 B -4 B (-1%)
build/block-library/blocks/button/style-rtl.css 549 B -51 B (-8%)
build/block-library/blocks/button/style.css 549 B -51 B (-8%)
build/block-library/blocks/buttons/editor-rtl.css 309 B -6 B (-2%)
build/block-library/blocks/buttons/editor.css 309 B -6 B (-2%)
build/block-library/blocks/buttons/style-rtl.css 317 B -53 B (-14%) 👏
build/block-library/blocks/buttons/style.css 317 B -53 B (-14%) 👏
build/block-library/blocks/cover/editor-rtl.css 546 B +69 B (+14%) ⚠️
build/block-library/blocks/cover/editor.css 547 B +67 B (+14%) ⚠️
build/block-library/blocks/navigation/editor-rtl.css 1.71 kB -5 B (0%)
build/block-library/blocks/navigation/editor.css 1.71 kB -7 B (0%)
build/block-library/blocks/navigation/style-rtl.css 1.64 kB +28 B (+2%)
build/block-library/blocks/navigation/style.css 1.64 kB +26 B (+2%)
build/block-library/blocks/post-author/editor-rtl.css 0 B -210 B (removed) 🏆
build/block-library/blocks/post-author/editor.css 0 B -210 B (removed) 🏆
build/block-library/blocks/post-author/style-rtl.css 175 B -7 B (-4%)
build/block-library/blocks/post-author/style.css 176 B -5 B (-3%)
build/block-library/blocks/query-title/editor-rtl.css 0 B -85 B (removed) 🏆
build/block-library/blocks/query-title/editor.css 0 B -85 B (removed) 🏆
build/block-library/blocks/social-link/editor-rtl.css 177 B +12 B (+7%) 🔍
build/block-library/blocks/social-link/editor.css 177 B +12 B (+7%) 🔍
build/block-library/blocks/social-links/editor-rtl.css 824 B +12 B (+1%)
build/block-library/blocks/social-links/editor.css 823 B +12 B (+1%)
build/block-library/blocks/social-links/style-rtl.css 1.32 kB +14 B (+1%)
build/block-library/blocks/social-links/style.css 1.32 kB +14 B (+1%)
build/block-library/blocks/template-part/editor-rtl.css 560 B -76 B (-12%) 👏
build/block-library/blocks/template-part/editor.css 559 B -76 B (-12%) 👏
build/block-library/blocks/term-description/editor-rtl.css 0 B -90 B (removed) 🏆
build/block-library/blocks/term-description/editor.css 0 B -90 B (removed) 🏆
build/block-library/common-rtl.css 815 B -38 B (-4%)
build/block-library/common.css 812 B -37 B (-4%)
build/block-library/editor-rtl.css 9.65 kB -142 B (-1%)
build/block-library/editor.css 9.65 kB -144 B (-1%)
build/block-library/index.min.js 148 kB +476 B (0%)
build/block-library/style-rtl.css 10.3 kB -87 B (-1%)
build/block-library/style.css 10.3 kB -87 B (-1%)
build/blocks/index.min.js 46 kB +245 B (+1%)
build/components/index.min.js 212 kB -4.82 kB (-2%)
build/components/style-rtl.css 15.3 kB +79 B (+1%)
build/components/style.css 15.3 kB +79 B (+1%)
build/compose/index.min.js 10.4 kB -3 B (0%)
build/core-data/index.min.js 12.4 kB +5 B (0%)
build/customize-widgets/index.min.js 11.2 kB +19 B (0%)
build/edit-navigation/index.min.js 15.8 kB +456 B (+3%)
build/edit-navigation/style-rtl.css 3.76 kB +21 B (+1%)
build/edit-navigation/style.css 3.76 kB +21 B (+1%)
build/edit-post/index.min.js 29.3 kB +11 B (0%)
build/edit-post/style-rtl.css 7.22 kB +24 B (0%)
build/edit-post/style.css 7.22 kB +25 B (0%)
build/edit-site/index.min.js 30 kB +460 B (+2%)
build/edit-site/style-rtl.css 5.56 kB +62 B (+1%)
build/edit-site/style.css 5.56 kB +64 B (+1%)
build/edit-widgets/index.min.js 15.8 kB +18 B (0%)
build/edit-widgets/style-rtl.css 4.12 kB +24 B (+1%)
build/edit-widgets/style.css 4.13 kB +25 B (+1%)
build/editor/index.min.js 37.5 kB +6 B (0%)
build/editor/style-rtl.css 3.78 kB +13 B (0%)
build/editor/style.css 3.77 kB +12 B (0%)
build/element/index.min.js 3.21 kB +42 B (+1%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 931 B
build/admin-manifest/index.min.js 1.09 kB
build/annotations/index.min.js 2.7 kB
build/api-fetch/index.min.js 2.21 kB
build/autop/index.min.js 2.08 kB
build/blob/index.min.js 459 B
build/block-directory/index.min.js 6.2 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 58 B
build/block-library/blocks/audio/editor.css 58 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 131 B
build/block-library/blocks/code/theme.css 131 B
build/block-library/blocks/columns/editor-rtl.css 206 B
build/block-library/blocks/columns/editor.css 205 B
build/block-library/blocks/columns/style-rtl.css 497 B
build/block-library/blocks/columns/style.css 496 B
build/block-library/blocks/cover/style-rtl.css 1.17 kB
build/block-library/blocks/cover/style.css 1.17 kB
build/block-library/blocks/embed/editor-rtl.css 488 B
build/block-library/blocks/embed/editor.css 488 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 322 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 977 B
build/block-library/blocks/gallery/editor.css 982 B
build/block-library/blocks/gallery/style-rtl.css 1.6 kB
build/block-library/blocks/gallery/style.css 1.59 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/home-link/style-rtl.css 247 B
build/block-library/blocks/home-link/style.css 247 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 502 B
build/block-library/blocks/image/style.css 505 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 528 B
build/block-library/blocks/latest-posts/style.css 527 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 568 B
build/block-library/blocks/navigation-link/editor.css 570 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 300 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/style-rtl.css 195 B
build/block-library/blocks/navigation-submenu/style.css 195 B
build/block-library/blocks/navigation-submenu/view.min.js 343 B
build/block-library/blocks/navigation/view.min.js 2.74 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 377 B
build/block-library/blocks/page-list/editor.css 377 B
build/block-library/blocks/page-list/style-rtl.css 198 B
build/block-library/blocks/page-list/style.css 198 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-comments-form/style-rtl.css 140 B
build/block-library/blocks/post-comments-form/style.css 140 B
build/block-library/blocks/post-comments/style-rtl.css 360 B
build/block-library/blocks/post-comments/style.css 359 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 396 B
build/block-library/blocks/post-featured-image/editor.css 397 B
build/block-library/blocks/post-featured-image/style-rtl.css 156 B
build/block-library/blocks/post-featured-image/style.css 156 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 391 B
build/block-library/blocks/post-template/style.css 392 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 60 B
build/block-library/blocks/post-title/style.css 60 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 378 B
build/block-library/blocks/pullquote/style.css 378 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 262 B
build/block-library/blocks/query-pagination/editor.css 255 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 187 B
build/block-library/blocks/quote/style.css 187 B
build/block-library/blocks/quote/theme-rtl.css 220 B
build/block-library/blocks/quote/theme.css 222 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 374 B
build/block-library/blocks/search/style.css 375 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 250 B
build/block-library/blocks/separator/style.css 250 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 769 B
build/block-library/blocks/site-logo/editor.css 769 B
build/block-library/blocks/site-logo/style-rtl.css 165 B
build/block-library/blocks/site-logo/style.css 165 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/spacer/editor-rtl.css 307 B
build/block-library/blocks/spacer/editor.css 307 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/theme-rtl.css 665 B
build/block-library/theme.css 669 B
build/block-serialization-default-parser/index.min.js 1.09 kB
build/block-serialization-spec-parser/index.min.js 2.79 kB
build/customize-widgets/style-rtl.css 1.5 kB
build/customize-widgets/style.css 1.49 kB
build/data-controls/index.min.js 614 B
build/data/index.min.js 7.1 kB
build/date/index.min.js 31.5 kB
build/deprecated/index.min.js 428 B
build/dom-ready/index.min.js 304 B
build/dom/index.min.js 4.46 kB
build/edit-post/classic-rtl.css 492 B
build/edit-post/classic.css 494 B
build/escape-html/index.min.js 517 B
build/format-library/index.min.js 5.93 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.55 kB
build/html-entities/index.min.js 424 B
build/i18n/index.min.js 3.6 kB
build/is-shallow-equal/index.min.js 501 B
build/keyboard-shortcuts/index.min.js 1.72 kB
build/keycodes/index.min.js 1.3 kB
build/list-reusable-blocks/index.min.js 1.85 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.92 kB
build/notices/index.min.js 845 B
build/nux/index.min.js 2.03 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.83 kB
build/primitives/index.min.js 921 B
build/priority-queue/index.min.js 582 B
build/react-i18n/index.min.js 671 B
build/redux-routine/index.min.js 2.63 kB
build/reusable-blocks/index.min.js 2.19 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.6 kB
build/server-side-render/index.min.js 1.52 kB
build/shortcode/index.min.js 1.48 kB
build/token-list/index.min.js 562 B
build/url/index.min.js 1.74 kB
build/viewport/index.min.js 1.02 kB
build/warning/index.min.js 248 B
build/widgets/index.min.js 7.11 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@anton-vlasenko anton-vlasenko changed the title [WIP] Add/preload more requests on the navigation screen Add/preload more requests on the navigation screen Oct 6, 2021
@anton-vlasenko anton-vlasenko requested a review from mmtr as a code owner October 6, 2021 15:58
@anton-vlasenko
Copy link
Contributor Author

We still have 1 XHR request left because we can only use data from the cache once.
Then, to avoid loading stale (outdated) data, the data gets deleted from the cache.
Please see this for reference.
So, when we fetch the data for the second time, we perform an actual fetch request (because we've removed the data from the cache).
I partially agree with this comment, and IMO, the proper solution to this would be to cache data at the wordpress/data level.

@anton-vlasenko anton-vlasenko changed the title Add/preload more requests on the navigation screen Navigation: Preload more API requests Oct 6, 2021
@anton-vlasenko anton-vlasenko changed the title Navigation: Preload more API requests Navigation: preload more API requests Oct 6, 2021
@noisysocks noisysocks added [Feature] Navigation Screen [Type] Bug An existing feature does not function as intended [Type] Performance Related to performance efforts labels Oct 7, 2021
@noisysocks
Copy link
Member

So, when we fetch the data for the second time, we perform an actual fetch request (because we've removed the data from the cache).

Why are we fetching data a second time?

Copy link
Member

@noisysocks noisysocks left a comment

Choose a reason for hiding this comment

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

Code LGTM and I can see only one XHR request in DevTools when I browse to the navigation screen 👍

Comment on lines 75 to 78
// Unsetting the cache key ensures that the data is only preloaded a single time
const cacheData = cache[ method ][ path ];
delete cache[ method ][ path ];
Copy link
Member

Choose a reason for hiding this comment

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

Tiny nit: would be nice to format this similarly to the above branch

Suggested change
// Unsetting the cache key ensures that the data is only preloaded a single time
const cacheData = cache[ method ][ path ];
delete cache[ method ][ path ];
const cacheData = cache[ method ][ path ];
// Unsetting the cache key ensures that the data is only preloaded a single time
delete cache[ method ][ path ];

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree.
Fixed in d71c48f

array(
'per_page' => $results_per_page,
'context' => 'edit',
'_locale' => 'user',
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is needed.
Cache records are matched by the path.
If we don't use this path, we will not be able to find the cache record.

Copy link
Contributor

@adamziel adamziel Oct 12, 2021

Choose a reason for hiding this comment

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

I don't like how the path must be kept in sync between two separate places (JS and PHP), but I also don't have any better ideas to offer. 😒

@spacedmonkey
Copy link
Member

How does this handle the fact the REST API preloaded, means that caches have to be bust. See #24642 (comment)

@anton-vlasenko
Copy link
Contributor Author

@spacedmonkey

How does this handle the fact the REST API preloaded, means that caches have to be bust. See #24642 (comment)

Thank you for the review.
To reply to your comment, I need to understand what you mean by "caches" when you say they have to be bust.
Do you mean browser cache or some other type of cache?

@spacedmonkey
Copy link
Member

By cache, I mean the preloading itself. That is a cache, it doesn't matter how many times you reload the api request, it will still come from the preloaded middleware. My worry, is that even if we update the menu items, that the data will not update, as it is cache.

By cache busting, i.e just add a query string to the api request, that will stop the request loading from middleware.

const cacheData = cache[ method ][ path ];

// Unsetting the cache key ensures that the data is only preloaded a single time
delete cache[ method ][ path ];
Copy link
Member

Choose a reason for hiding this comment

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

This is a very big change to how preloading works. Are we saying here, after the first load, preload cache is removed. What about other problems of the code that may call the same rest api twice.

Cc @swissspidy

Copy link
Member

Choose a reason for hiding this comment

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

This is not new/changed behavior? See

// Unsetting the cache key ensures that the data is only preloaded a single time

Copy link
Member

Choose a reason for hiding this comment

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

How is not a new behavior?

Copy link
Member

Choose a reason for hiding this comment

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

That‘s from a few lines above. Sounds like it was not fully implemented there.

so this change here looks reasonable 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a cache, it doesn't matter how many times you reload the api request, it will still come from the preloaded middleware.

This is not how it works at the moment.
We delete the cache data from the preloadedData object the first time we use the data, so nothing gets cached and we don't need to bust the caches.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What about other problems of the code that may call the same rest api twice.

Then we need to fix those parts of the code that call the same REST API twice. We should call the same REST API twice only if it's absolutely needed.
IMO preloading cannot fix everything.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this here updates the cache logic for OPTIONS requests to match the behavior for GET requests. All reasonable to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

Annoying as it is we should consider pulling this out into a separate PR. That way it will be easier to notice (and rollback) if this introduces any unforeseen bugs.

Copy link
Member

Choose a reason for hiding this comment

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

That way it will be easier to notice (and rollback) if this introduces any unforeseen bugs.

Other packages and scripts may use this package, this change of behaviour may break something. This needs to be a changelog and well documented.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've moved this code into a separate PR.
#35527
We should be good to merge now.

@spacedmonkey
Copy link
Member

Screenshot 2021-10-10 at 00 19 57

I see this error when I checkout this branch and build it.

Also I am seeing PHPUnit tests failing.

lib/editor-settings.php Outdated Show resolved Hide resolved
* @param int $menu_id Menu ID.
* @return string
*/
function gutenberg_navigation_get_menu_endpoint( $menu_id ) {
Copy link
Member

Choose a reason for hiding this comment

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

Where are these functions used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nowhere :) I was going to delete it. Good catch.

Comment on lines 75 to 78
const cacheData = cache[ method ][ path ];

// Unsetting the cache key ensures that the data is only preloaded a single time
delete cache[ method ][ path ];
Copy link
Member

Choose a reason for hiding this comment

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

Should this be mentioned in the changelog? Bummer that #25550 didn't do that originally either

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it should. I will move this fix to a new PR.

Copy link
Member

@spacedmonkey spacedmonkey left a comment

Choose a reason for hiding this comment

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

Approved with a nicpic 🎉

/**
* This function returns an url for the /wp/v2/types endpoint
*
* @since 11.6.0
Copy link
Contributor

Choose a reason for hiding this comment

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

It's going to be 11.8 now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 83a1edc

Comment on lines 156 to 161
$menus_data = empty( $preload_data[ $menus_data_path ] ) ?
array()
:
array(
$menus_data_path => $preload_data[ $menus_data_path ],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

That's such a nit pick, but I find this ternary formatting pretty hard to read. Perhaps an if/else would be more readable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. It looks ugly.
I've fixed this, and it seems we don't need if/else here, if is enough.

* It uses data that is returned from the /__experimental/menus endpoint for requests
* to the /__experimental/menu/<menuId> endpoint, because the data is the same.
* This way, we can avoid making additional REST API requests.
* This middleware can be removed if/when we implement caching at the wordpress/data level.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you create a ticket for caching at the wordpress/data level that so that we don't forget about it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, #35601

Comment on lines 49 to 52
if ( ! key ) {
return next( options );
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this check useful here?

Suggested change
if ( ! key ) {
return next( options );
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's better safe than sorry, but I agree, we don't need that.

return id === menuId;
} );

if ( 0 < menu.length ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yoda conditions don't read well, e.g. here it's "if zero is less than menu length then." I know there are some guidelines to do that on PHP side to prevent accidental assignments like $a = 4 instead of $a === 5, but these days linters are pretty good at catching that (and also it doesn't apply to <).

Suggested change
if ( 0 < menu.length ) {
if ( menu.length ) {

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Oct 12, 2021

Choose a reason for hiding this comment

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

Yoda conditions don't read well,

I see what you mean. I can agree it can be harder to read Yoda conditions for some people.
Although Yoda conditions have their advantages and it's not a crime to use them. IMO it's just a personal preference.

if ( value > 0 ) is better than just if ( value ), because the former is a stricter comparison.
If value === -1, the latter expression will return true, which is not what it is supposed to do.
This can happen if we compare some unsigned numbers like bank account amounts.
I understand that length of the array cannot be lower than zero. But I prefer to write it like that everywhere because it will work correctly regardless of whether we compare unsigned or signed integers.

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Oct 12, 2021

Choose a reason for hiding this comment

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

Fixed (?) in 612d17b

@adamziel
Copy link
Contributor

adamziel commented Oct 12, 2021

My concern here is that we'll soon end up with createWidgetsPreloadingMiddleware, createPostsPreloadingMiddleware and so on. Reusing data from the list endpoint in another endpoint sounds like a common use case – how is this solved in different places? If it isn't, are there any other places that could use such a mechanism? If so, could this be incorporated into the existing createPreloadingMiddleware

*
* @param Array $preload_data Array containing the preloaded data.
*/
$preload_data = apply_filters( "{$editor_name}_preload_data", $preload_data );
Copy link
Member

Choose a reason for hiding this comment

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

Is this a new filter? We should try to avoid using dynamic filter names.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, isn't this already filterable in block_editor_rest_api_preload actually?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is a new filter.

We should try to avoid using dynamic filter names.

Good to know.
Fixed in 75519d9

Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this filter then? Why isn't it covered by block_editor_rest_api_preload?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@TimothyBJacobs
It isn't covered by block_editor_rest_api_preload function because we need to modify the data that gets passed into the 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );'.
My bad, I didn't notice you mentioned block_editor_rest_api_preload the first time.
I can refactor this to use block_editor_rest_api_preload instead, but:

  1. I will have to add a new filter into the body of the block_editor_rest_api_preload function because of the reason above.
  2. We will break backward compatibility because we will have to get rid of the
$preload_paths = apply_filters( "{$editor_name}_preload_paths", $settings['preload_paths'] );

call.
The block_editor_rest_api_preload function will replace all that code.
We need to refactor this, but I'm not sure about breaking BC. It seems that we don't use the filter above ({$editor_name}_preload_paths).
What are your thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

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

What I'm having trouble understanding is why we can't use the block_editor_rest_api_preload_paths filter in block_editor_rest_api_preload. Why do we need another filter on a list of preloaded paths.

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Oct 15, 2021

Choose a reason for hiding this comment

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

@TimothyBJacobs

This is not a filter on a list of preloaded paths.
This is the filter that allows modifying already preloaded data.
I've created createMenuPreloadingMiddleware middleware, and it needs its own set of data, i.e., a list of all menus.
That filter allows modifying preloaded data so that list of all menus doesn't get passed into the createPreloadingMiddleware.
Instead, we need to pass it into the createMenuPreloadingMiddleware middleware and process it there.
We have to either push this PR with that additional filter OR remove that filter and the custom middleware (createMenuPreloadingMiddleware).
In such a case, we won't be able to preload /__experimenta/menu/<menuId> endpoints properly until we find a better solution.
What is your opinion on this?

anton-vlasenko and others added 6 commits October 12, 2021 18:15
Co-authored-by: Adam Zielinski <adam@adamziel.com>
2. Don't use variable filter name. Pass the ditor_name as the second parameter.
2. Remove redundant code.
Co-authored-by: Jonny Harris <spacedmonkey@users.noreply.github.com>
@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Oct 13, 2021

@noisysocks

So, when we fetch the data for the second time, we perform an actual fetch request (because we've removed the data from the cache).

Why are we fetching data a second time?

It's hard to tell why, but both requests are coming from this place: https://github.com/WordPress/gutenberg/blob/trunk/packages/core-data/src/resolvers.js#L105
I've created an issue so that we don't forget about it.
#35601

*
* @return {string} Normalized path.
*/
export function getStablePath( path ) {
Copy link
Contributor

@talldan talldan Oct 14, 2021

Choose a reason for hiding this comment

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

Looking at the way this function works, it's worth checking some of the utils in the url package. It might be possible to make use of getQueryString or getQueryArgs from that package.

Some of this code might also be a good candidate for the url package. I could see a normalizeQueryString function extracted from this being a useful utility. There might also be overlap with buildQueryString does that already order query string args?

Might be a good thing for a separate follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome! I've created a GH issue for this. #35799

* @param {Object} preloadedData
* @return {Function} Preloading middleware.
*/
export function createMenuPreloadingMiddleware( preloadedData ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Even though edit-navigation isn't a public package, I'm uneasy about exporting this as a stable API because it's very specific to menus and the navigation editor.

It seems like a more general problem that when we preload a request that lists items the individual item can also be considered preloaded, regardless of the REST resource. It might be something that applies to posts, pages and other resources too. What do you think?

I'm not against merging this PR with a special case for menus right now, but in the long run I could see this as a candidate for removal if we come up with a more generalised preloading system.

Right now it might be worth exporting this as __unstableCreateMenuPreloadingMiddleware so that it doesn't accidentally end up as a stable API that has to be supported indefinitely.

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Oct 14, 2021

Choose a reason for hiding this comment

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

I'd wait until #15105 gets merged and remove createMenuPreloadingMIddleware altogether.
When I started to work on this issue I wanted to implement something like that. I didn't know this PR already existed.
Thank you for the review.

Copy link
Contributor

Choose a reason for hiding this comment

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

#15105 is an issue and seems unrelated. Did you mean another number?

Copy link
Contributor

@talldan talldan Oct 15, 2021

Choose a reason for hiding this comment

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

@adamziel The related discussion is here - #35402 (comment), the background is that we'd be able to preload the selected menu item if it were stored in the database instead of local storage, and I guess that removes the use case for this function? Though I still feel like we'll be in a situation where we need to load all the menus for the menu switcher.

@anton-vlasenko I wouldn't worry too much about waiting for #15105 / #19177 because it has been a very long-running issue, but it'd be good to help it along if we can.

If we can ship this PR by renaming the function I think that's fine 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@adamziel

#15105 is an issue and seems unrelated. Did you mean another number?

It seems to be related. If it allows to store selectedMenuId to the database and then fetch it from there, it will solve all the issues with preloading.
We have to tinker with createMenuPreloadingMiddleware only because we don't know which menu is currently selected when preloading data.

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Oct 15, 2021

Choose a reason for hiding this comment

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

Thank you for the feedback, @talldan.
I will surely rename that method. (UPD: fixed in 41ea183).
I'm waiting for @TimothyBJacobs 's feedback on this comment. #35402 (comment)

);

if ( $first_menu_id ) {
$preload_paths[] = gutenberg_navigation_get_menu_items_endpoint( $first_menu_id );
Copy link
Contributor

@talldan talldan Oct 14, 2021

Choose a reason for hiding this comment

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

I see there was already a resolved conversation about this, but I think it might be worth creating an issue to explore how the selected menu might be preloaded.

A thought I had is that we could pass the menu id as a query string http://localhost:8888/wp-admin/nav-menus.php?menuId=2, but it'd require every link to the editor to have that query string 🤯 . Deep linking to a menu would be cool though. A query string wouldn't be perfect, even better would be a proper route like http://localhost:8888/wp-admin/menus/2.

Or alternatively we could help with #15105, at which point the preference would be stored in the database rather than localStorage, and our server-side code would be able to use the value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or alternatively we could help with #15105, at which point the preference would be stored in the database rather than localStorage, and our server-side code would be able to use the value.

That PR will be a game-changer if it works as you described. Then we could just use selectedMenuId from the database to preload data and get rid of the createMenuPreloadingMiddleware altogether.

@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Oct 15, 2021

@adamziel

My concern here is that we'll soon end up with createWidgetsPreloadingMiddleware, createPostsPreloadingMiddleware and so on. Reusing data from the list endpoint in another endpoint sounds like a common use case – how is this solved in different places?

I'm not aware of how this is solved in different places.
I haven't been able to find any examples.

If it isn't, are there any other places that could use such a mechanism? If so, could this be incorporated into the existing createPreloadingMiddleware

I've tried to incorporate this into the createPreloadingMiddleware via callback.
I implemented a callback that allows modifying preloaded data. But, there were issues with stale data if a user loads the Navigation Editor page and tries to switch to another menu.
It's because we need to use menu data only once. Unfortunately, callbacks don't allow us to delete that data after it was used.
I find it difficult to explain, but callbacks are not likely going to work.

We don't want it to become a stable API for now.
See #35402 (comment)
@anton-vlasenko anton-vlasenko merged commit 277fee8 into trunk Oct 19, 2021
@anton-vlasenko anton-vlasenko deleted the add/preload-more-requests-on-the-navigation-screen branch October 19, 2021 13:45
@TimothyBJacobs
Copy link
Member

The structure of this PR is confusing to me. I don't understand why we are introducing another filter in the block editor settings. I understand that this is filtering the preloaded data, instead of the set of paths. But I don't think that's the API that we should be exposing to 3rd party developers since it's a lot lower level.

It seems like these permanent APIs are being added to solve temporary deficiencies in core-data which is unfortunate.

Have we considered preloading the different menus as separate requests?

@anton-vlasenko
Copy link
Contributor Author

@TimothyBJacobs

The structure of this PR is confusing to me.

Yes, let's discuss it and maybe create follow-up tasks (given the age of this PR).

Have we considered preloading the different menus as separate requests?

I'm not sure I understand.
Per @spacedmonkey's comment, the idea is to remove such calls.

We should also remove call to __experimental/menus/<current_menu_id> as this data will be loaded in __experimental/menus

What is your idea exactly?

BTW, I've just realized we can get rid of that additional filter.
If we load the menu data needed for the createMenuPreloadingMiddleware function sideways, somewhere inside the gutenberg_navigation_init function, we don't need that additional filter.
We will have to call the code below again, but this way we can get rid of that additional filter.

// Of course this snippet has to be refined, it's just for example
$preload_data = array_reduce(
		['/__experimental/menus'],
		'rest_preload_api_request',
		array()
	);

Would you please let me know if that would be an acceptable solution?

@TimothyBJacobs
Copy link
Member

Have we considered preloading the different menus as separate requests?

I'm not sure I understand. Per @spacedmonkey's comment, the idea is to remove such calls.

We should also remove call to __experimental/menus/<current_menu_id> as this data will be loaded in __experimental/menus

What is your idea exactly?

Yeah, I don't have an issue with that menus/<current> route being preloaded even if we do also preload the menus collection. Though of course, preferably core-data should be able to just handle this. Is there a ticket for that?

BTW, I've just realized we can get rid of that additional filter. If we load the menu data needed for the createMenuPreloadingMiddleware function sideways, somewhere inside the gutenberg_navigation_init function, we don't need that additional filter. We will have to call the code below again, but this way we can get rid of that additional filter.

// Of course this snippet has to be refined, it's just for example
$preload_data = array_reduce(
		['/__experimental/menus'],
		'rest_preload_api_request',
		array()
	);

Would you please let me know if that would be an acceptable solution?

Yeah, not introducing that filter and implementing it as you suggest would be preferable to me.

@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Oct 19, 2021

Is there a ticket for that?

Yes, I created it several days ago. #35601 (feel free to add your notes to it).
I'd like to explain it a bit.
When we load the navigation editor page, we make 2 duplicate calls to the menus/<current> endpoint.
And we don't make any calls to that endpoint when we change the currently selected menu.
So, I think the issue is somewhere inside the React components. It seems we don't need to call that menus/<current> at all. But we need to investigate it and understand if we can get rid of these duplicate calls.
We may not even need to refactor core-data.

Yeah, not introducing that filter and implementing it as you suggest would be preferable to me.

Awesome! I will create a PR and an issue to refactor this.

@anton-vlasenko
Copy link
Contributor Author

@TimothyBJacobs

Yeah, not introducing that filter and implementing it as you suggest would be preferable to me.

So, here is the PR that removes that filter. I'd appreciate your review.
#35838

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Bug An existing feature does not function as intended [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Navigation: Preload API requests
8 participants