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

Add new preferences persistence API, and save editor preferences in user meta #39795

Merged
merged 74 commits into from
Apr 27, 2022

Conversation

talldan
Copy link
Contributor

@talldan talldan commented Mar 28, 2022

Related - #31965
Also related - #39672 (comment)

Fixes #15105
Fixes #34405

What?

This PR explores moving away from the current @wordpress/data way of persisting store data to local storage.

It implements a new API in the preferences package for configuring how the store state is persisted.

It adds a new package that persists preferences to the WordPress database, as part of the user's meta.

And then finally this is all configured using an inline script.

Why?

A good reason is that this will finally allow #15105 to be closed, which is a long standing problem for users.

There are also some technical advantages that this will unlock:

  • deprecating the old persistence system in @wordpress/data
  • deprecating registerStorein @wordpress/data in favor of register

How?

Adds a new setPersistenceLayer action to the preferences package. A higher order reducer is then used to keep state in sync with the persistence layer in the preferences store.

There's also a new preferences-persistence package (based on #19177). This saves data to user meta via the REST API primarily. It also saves data to local storage as a back in case the user goes offline or a request is interrupted.

Finally, I've moved all the migration code over from the old package to this new package, any old preferences should be retained.

Testing Instructions

  1. Open up two different browsers side by side.
  2. Login to your test environment in both browsers using the same login and edit a post, site or widgets
  3. Change some settings in one browser (i.e. top toolbar)
  4. Reload the other browser and you should see the change reflected.

@github-actions
Copy link

github-actions bot commented Mar 28, 2022

Size Change: -616 B (0%)

Total Size: 1.23 MB

Filename Size Change
build/block-editor/index.min.js 151 kB +14 B (0%)
build/block-editor/style-rtl.css 15.1 kB -9 B (0%)
build/block-editor/style.css 15.1 kB -11 B (0%)
build/block-library/editor-rtl.css 10.3 kB +6 B (0%)
build/block-library/editor.css 10.3 kB +6 B (0%)
build/block-library/index.min.js 177 kB +246 B (0%)
build/data/index.min.js 7.64 kB -1.01 kB (-12%) 👏
build/edit-site/style-rtl.css 7.95 kB +3 B (0%)
build/edit-site/style.css 7.93 kB +4 B (0%)
build/editor/index.min.js 38.5 kB +11 B (0%)
build/preferences/index.min.js 1.32 kB +121 B (+10%) ⚠️
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.51 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 150 B
build/block-library/blocks/audio/editor.css 150 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/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 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 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.53 kB
build/block-library/blocks/cover/style.css 1.53 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 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 353 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 961 B
build/block-library/blocks/gallery/editor.css 964 B
build/block-library/blocks/gallery/style-rtl.css 1.51 kB
build/block-library/blocks/gallery/style.css 1.51 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 333 B
build/block-library/blocks/group/editor.css 333 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 76 B
build/block-library/blocks/heading/style.css 76 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 529 B
build/block-library/blocks/image/style.css 535 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 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 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 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.93 kB
build/block-library/blocks/navigation/style.css 1.92 kB
build/block-library/blocks/navigation/view-modal.min.js 2.65 kB
build/block-library/blocks/navigation/view.min.js 395 B
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 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 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 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 69 B
build/block-library/blocks/post-comments-form/editor.css 69 B
build/block-library/blocks/post-comments-form/style-rtl.css 521 B
build/block-library/blocks/post-comments-form/style.css 521 B
build/block-library/blocks/post-comments/editor-rtl.css 77 B
build/block-library/blocks/post-comments/editor.css 77 B
build/block-library/blocks/post-comments/style-rtl.css 511 B
build/block-library/blocks/post-comments/style.css 511 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 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 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 323 B
build/block-library/blocks/post-template/style.css 323 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 80 B
build/block-library/blocks/post-title/style.css 80 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 370 B
build/block-library/blocks/pullquote/style.css 370 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 221 B
build/block-library/blocks/query-pagination/editor.css 211 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 369 B
build/block-library/blocks/query/editor.css 369 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 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 397 B
build/block-library/blocks/search/style.css 398 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 140 B
build/block-library/blocks/separator/editor.css 140 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 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 759 B
build/block-library/blocks/site-logo/editor.css 759 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 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/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 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 504 B
build/block-library/blocks/table/editor.css 504 B
build/block-library/blocks/table/style-rtl.css 625 B
build/block-library/blocks/table/style.css 625 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 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 149 B
build/block-library/blocks/template-part/editor.css 149 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/common-rtl.css 993 B
build/block-library/common.css 990 B
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 11.5 kB
build/block-library/style.css 11.5 kB
build/block-library/theme-rtl.css 689 B
build/block-library/theme.css 694 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47 kB
build/components/index.min.js 222 kB
build/components/style-rtl.css 15 kB
build/components/style.css 15 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 14.5 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/date/index.min.js 32 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.58 kB
build/edit-navigation/index.min.js 15.8 kB
build/edit-navigation/style-rtl.css 4.05 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 30.1 kB
build/edit-post/style-rtl.css 7.11 kB
build/edit-post/style.css 7.11 kB
build/edit-site/index.min.js 47.5 kB
build/edit-widgets/index.min.js 16.3 kB
build/edit-widgets/style-rtl.css 4.41 kB
build/edit-widgets/style.css 4.4 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 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.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences-persistence/index.min.js 2.1 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 628 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.2 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@talldan
Copy link
Contributor Author

talldan commented Mar 29, 2022

The more I'm working on this, the more I'm thinking that the ideal setup will be two packages:

  • wordpress/preferences - a general purpose key/value data store that persists to local storage out of the box, but the persistence layer is configurable. Has no WordPress-specific code.
  • wordpress/wp-preferences - WordPress specific utils for wordpress/preferences. Contains a persistence layer for the WordPress database, all the code for the WordPress data migrations, and exports react components that use preferences. It could possibly have its own php script for preloading preferences that are in the database.

This would entail moving the react components that are already in wordpress/preferences to this new package, but that should be ok if they're deprecated for two plugin versions and don't land in core. I think it'd be good to keep the main preferences package only as a data store (reducing it's only proper dependency to @wordpress/data).

@talldan talldan force-pushed the try/preferences-configurable-persistence-layer branch from bc432e3 to 6498c0e Compare March 30, 2022 07:54
@talldan
Copy link
Contributor Author

talldan commented Mar 30, 2022

@youknowriad This is now working using an inline script, but there might be a better way of doing it (see my two comments above).

Seems to work pretty well in usage though. We can also preload the preferences via the script, which might be a safer option compared to relying on the api fetch preloading.

$preload_data = get_user_meta( $user_id, $meta_key, true );

wp_add_inline_script(
'wp-database-persistence-layer',
Copy link
Contributor

@youknowriad youknowriad Mar 31, 2022

Choose a reason for hiding this comment

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

I don't understand why you chose to add this to wp-database-persistence-layer instead of wp-preferences (which mean you don't have to add any dependencies to any other script than wp-preferences)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, true. I'm still learning about the best way to do this. Thanks for the pointers.

@talldan talldan force-pushed the try/preferences-configurable-persistence-layer branch from 5e42c97 to 218d002 Compare March 31, 2022 07:22
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.

This is looking very promising for me

@@ -0,0 +1,3 @@
# Database persistence layer
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking about the name here:

  • Should the name include "preferences" to clarify that it's about persisting preferences
  • Should the name include "api" instead of "database" (I think that's probably arguable)?

Copy link
Contributor Author

@talldan talldan Apr 1, 2022

Choose a reason for hiding this comment

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

Should the name include "preferences" to clarify that it's about persisting preferences

I'd like to add data migration code to the package that's specific to preferences, so I think it makes sense to mention preferences.

A problem with api and database is that those names are about the implementation. If we wanted to change it in future we'd probably need a new package.

We could go with something like preferences-persistence-layer and in the README state that this is the persistence layer for WordPress preferences. Maybe it exports createUserMetaPersistenceLayer, and if in the future we want to persist preferences in a different way, a new function can be added and the old one can be deprecated.

Copy link
Contributor

Choose a reason for hiding this comment

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

the problem with preferences-persistence-layer is that it a local storage is also a persistence layer. I'm fine keeping "database" or "server" or something like that.

Copy link
Member

Choose a reason for hiding this comment

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

A problem with api and database is that those names are about the implementation. If we wanted to change it in future we'd probably need a new package.

It's hard to imagine that this implementation would change for WordPress in the future. Anyway, based on the keywords used in the code and your discussion I wanted to check what do you think about the following ideas:

  • @wordpress/user-preferences
  • @wordpress/preferences-user-persistence

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 quite like those two suggestions @gziolo. Do you have a preference @youknowriad?

Copy link
Contributor

Choose a reason for hiding this comment

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

What about just @wordpress/preferences-persistence ?

Copy link
Member

@noisysocks noisysocks Apr 21, 2022

Choose a reason for hiding this comment

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

My 2¢ on naming:

Agree with Riad's thinking. There's two things to name.

The first thing is the abstraction we're introducing to @wordpress/preferences. You've chosen persistence layer. I think it might be clearer to call this something like preferences backend or preferences store. Using the word preferences makes it clear that @wordpress/preferences is what we're talking about.

The second thing is the type of the above abstraction that puts preferences into the database. You've chosen database which I think is a little misleading because it really uses the REST API. I doubt that will ever change.

So I'd go with rest-api-preferences-store or something along those lines.


BUT all the names suggested so far are a bit weird when you consider that this new package also contains all of the migrations for legacy local storage preferences (src/migrations/legacy-local-storage-data). Maybe that stuff should move to to the package where the legacy calls to localStorage used to live? (@wordpress/editor I think?)

Copy link
Contributor Author

@talldan talldan Apr 26, 2022

Choose a reason for hiding this comment

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

You've chosen database which I think is a little misleading because it really uses the REST API. I doubt that will ever change.

I'm not so sure about that, a switch to GraphQL, websockets, or webRTC could all make that name irrelevant. It would be a very big change for WordPress, but then using a realtime communication protocol is very likely to become a reality for collaborative editing. While it's unlikely everything would go through such an API, I think it's enough of a reason not to consider 'REST API' in the naming.

It's this kind of reason that most other packages are named for what they do rather than how they do it. There are exceptions, but most of those are very small utils. Because of that I would favour something like preferences-persistence or preferences-user-persistence.

I think I'll go with preferences-persistence as Riad suggested, just to keep it simple.

@talldan talldan force-pushed the try/preferences-configurable-persistence-layer branch from 21000ec to aa1a2bf Compare April 1, 2022 06:12
@talldan
Copy link
Contributor Author

talldan commented Apr 1, 2022

I think the reason why a lot of the tests have been failing is due to the way the page reloads after toggling a preference here in the createNewPost util:

if ( showWelcomeGuide !== isWelcomeGuideActive ) {
await page.evaluate( () =>
wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' )
);
await page.reload();
await page.waitForSelector( '.edit-post-layout' );
}

I think that's preventing the API request that would persist the change to preferences. After the page reloads it's like toggleFeature never happened.

An option could be to use navigator.sendBeacon for the API requests - https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon. This still sends a request even when the page unloads. It would mean missing out on APIFetch middleware.

Or maybe we just accept that this may happen and make the tests more robust. 🤔

edit: Another option still could be to use local storage as well as a fallback layer. There would need to be a way to know whether local storage is more recent than the database.

@talldan
Copy link
Contributor Author

talldan commented Apr 4, 2022

Still a lot of e2e tests failing. I think another reason for that might be that the tests clear localStorage which in trunk would reset preferences:

async function setupBrowser() {
await clearLocalStorage();
await setBrowserViewport( 'large' );
}

But here the preferences are persisted between test runs in the database.

@talldan talldan force-pushed the try/preferences-configurable-persistence-layer branch from 6424138 to 081604a Compare April 4, 2022 05:30
@talldan
Copy link
Contributor Author

talldan commented Apr 4, 2022

Still getting a lot of test failures.

There are also still some tests that do a page.reload and are they are racing and winning against the API request to user meta (specifically the one to disable the welcome guide that every test does). Because the preference isn't saved in time, when the editor reloads the welcome guide is unexpectedly open.

There are other test failures that are a bit more obscure. I'm having a hard time reproducing and/or understanding those.

It'd be good to get an idea of whether there are specifically bugs that are likely to happen for humans, or if these failures are only a result of the tests running so quickly.

@talldan talldan force-pushed the try/preferences-configurable-persistence-layer branch 2 times, most recently from 59f5dea to 70eb336 Compare April 5, 2022 08:23
@gziolo gziolo added the [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible label Apr 6, 2022
@talldan
Copy link
Contributor Author

talldan commented Apr 6, 2022

I think the e2e tests are mostly passing now. The tests did need some extra setup code to clear server stored preferences before and after test runs, so that was one of the main things that was missing.

Even then, solely using an async server API wasn't stable enough, there were still issues like page reloads in the tests racing and beating the preferences API request, which meant after the reload the editor state would be incorrect.

These would have been edge cases for human users, but to make things more robust I've made the preferences persist to user meta and also local storage as a backup. If for some reason the user meta preferences are outdated, the local storage will be used instead.

@youknowriad
Copy link
Contributor

there were still issues like page reloads in the tests racing and beating the preferences API request, which meant after the reload the editor state would be incorrect.

I feel like I had some things like that in page reloads for other stuff as well. It might good to have our own dedicated "reload" helper where we could inject things safely and not update every test at some point. (or maybe you did that I didn't check :P )

These would have been edge cases for human users, but to make things more robust I've made the preferences persist to user meta and also local storage as a backup. If for some reason the user meta preferences are outdated, the local storage will be used instead.

This is cool but makes me wonder if it's better to actually "preload" the preferences and make them "blocking" for the UI. Only show the UI once they're loaded. I'm thinking about folks loading their editor in a new browser and noticing a "jump" when the preferences load. Still not sure what's best though.

@talldan
Copy link
Contributor Author

talldan commented Apr 6, 2022

I feel like I had some things like that in page reloads for other stuff as well. It might good to have our own dedicated "reload" helper where we could inject things safely and not update every test at some point. (or maybe you did that I didn't check :P )

@youknowriad Do you remember what the other stuff was? I did think about this option. Waiting for network requests to complete before a reload might be a start.

This is cool but makes me wonder if it's better to actually "preload" the preferences and make them "blocking" for the UI. Only show the UI once they're loaded. I'm thinking about folks loading their editor in a new browser and noticing a "jump" when the preferences load. Still not sure what's best though.

The good news is that they are preloaded.

The issue is more that when the page reloads, a request to save preferences can be in flight and not finish before the page has reloaded. This is especially true because some tests are toggling a lot of preferences quickly (opening and closing panels and sidebars), and the server requests are debounced to avoid overwhelming the server, so the last timeout or request can be delayed or interrupted.

@youknowriad
Copy link
Contributor

Do you remember what the other stuff was?

I don't know but for me, it was more about doing something after the "reload". For instance I see await page.waitForSelector( '.edit-post-layout' ); is almost always called right after the reload function.

The good news is that they are preloaded.
The issue is more that when the page reloads, a request to save preferences can be in flight and not finish before the page has reloaded. This is especially true because some tests are toggling a lot of preferences quickly (opening and closing panels and sidebars), and the server requests are debounced to avoid overwhelming the server, so the last timeout or request can be delayed or interrupted.

👍

@talldan
Copy link
Contributor Author

talldan commented Apr 7, 2022

This seems to be working well now. I've added some code to migrate existing local storage preferences to the new system.

To finish up I'll add docs, tests and changelog updates. Then I'll consider splitting the PR up into smaller PRs (it is quite big, though it's mostly tests at the moment).

There's also a need to consider the naming of the new package, so I'll put some more thought into that.

@Mamaduka
Copy link
Member

Is it possible to disable persisting preferences in DB for some cases?

I noticed that an API request is triggered every time switch between a Document and a block.

CleanShot.2022-04-28.at.10.29.13.mp4

@talldan
Copy link
Contributor Author

talldan commented Apr 28, 2022

@Mamaduka It's a valid concern. At the moment there isn't a way to handle this. I think it would require some extra configuration for particular preferences.

A possibility is that some preferences don't trigger a request. They could save to local storage, and maybe they do persist to user meta too, but they could only update when another preference is saved AND/OR only update after a particular period of time AND/OR only update on browser unload (it's difficult to ensure this works though).

@Mamaduka
Copy link
Member

I was thinking about a similar method. For example, we could use post autosave to trigger preference syncing with user meta.

@youknowriad
Copy link
Contributor

I noticed that an API request is triggered every time switch between a Document and a block.

Interesting, I wonder if that's the reason for the performance gain (now that we don't use localstorage which might have been synchronous)

Anyway, it does feel like switching between sidebars shouldn't be a preference at all. Whether the sidebar is closed or open should be, so there's a small refactoring to be done here to improve things.

@gziolo
Copy link
Member

gziolo commented Apr 29, 2022

I opened #40715 with minor edits to the package configuration.

@noisysocks
Copy link
Member

Anyway, it does feel like switching between sidebars shouldn't be a preference at all. Whether the sidebar is closed or open should be, so there's a small refactoring to be done here to improve things.

Yeah agree this doesn't need to be persisted across sessions.

@talldan
Copy link
Contributor Author

talldan commented May 11, 2022

Anyway, it does feel like switching between sidebars shouldn't be a preference at all. Whether the sidebar is closed or open should be, so there's a small refactoring to be done here to improve things.

I've made a PR that does this - #40923

@talldan
Copy link
Contributor Author

talldan commented May 11, 2022

There may be a few other things we also want to refactor in this way. Expanded/collapsed panels in the post editor's document settings is something we could consider removing IMO.

I also have a PR to make the block editor package's insertUsage use the preferences package - #39632.

It would result in a REST API request every time a block is inserted. This might be a more challenging one to solve.

@Mamaduka
Copy link
Member

There may be a few other things we also want to refactor in this way. Expanded/collapsed panels in the post editor's document settings is something we could consider removing IMO.

I also noticed that.

Maybe API can support a list of preferences that don't need to be persisted immediately.

sprintf(
'( function() {
var serverData = %s;
var userId = "%s";
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor and belated, but it seems safer to encode userId formally, in the same way as serverData, even if we know that the result

sprintf( 'userId = %s', wp_json_encode( $user_id ) );

would be identifical to the current form

sprintf( 'userId = "%s"', $user_id );

The former doesn't rely on our own mental type checking, and redundancy helps with safety — especially around code interpolation. :)

@talldan talldan added the Needs Dev Note Requires a developer note for a major WordPress release cycle label Sep 13, 2022
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Sep 15, 2022
Adds a new feature to persist editor UI preferences between page loads and browsers.

* Adds a new preferences persistence API.
* Saves editor preferences in user meta instead of in browser's local storage.

Why?
Due to the transient nature of browser storage, this persistence is not as sticky as it is expected to be, including: switching browsers (unique storage between browsers), or using private browsing tabs (storage cleared between sessions), or the same user across a network of sites (storage unique by domain).

This is a backport from Gutenberg.[WordPress/gutenberg#39795 See WordPress/gutenberg PR 39795].

Props talldanwp, youknowriad, noisysocks, mamaduka, costdev, ironprogrammer, hellofromTonya.
See #56467.
Built from https://develop.svn.wordpress.org/trunk@54182


git-svn-id: http://core.svn.wordpress.org/trunk@53741 1a063a9b-81f0-0310-95a4-ce76da25c4cd
github-actions bot pushed a commit to gilzow/wordpress-performance that referenced this pull request Sep 15, 2022
Adds a new feature to persist editor UI preferences between page loads and browsers.

* Adds a new preferences persistence API.
* Saves editor preferences in user meta instead of in browser's local storage.

Why?
Due to the transient nature of browser storage, this persistence is not as sticky as it is expected to be, including: switching browsers (unique storage between browsers), or using private browsing tabs (storage cleared between sessions), or the same user across a network of sites (storage unique by domain).

This is a backport from Gutenberg.[WordPress/gutenberg#39795 See WordPress/gutenberg PR 39795].

Props talldanwp, youknowriad, noisysocks, mamaduka, costdev, ironprogrammer, hellofromTonya.
See #56467.
Built from https://develop.svn.wordpress.org/trunk@54182


git-svn-id: https://core.svn.wordpress.org/trunk@53741 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@swissspidy
Copy link
Member

@talldan Curious, why doesn’t this use the existing user settings API in WordPress (get_all_user_settings() and friends)? These are already stored in user meta.

@talldan
Copy link
Contributor Author

talldan commented Sep 16, 2022

@swissspidy There was a past attempt to use user settings, which discusses some of the reasons - #15800.

whereiscodedude pushed a commit to whereiscodedude/wpss that referenced this pull request Sep 18, 2022
Adds a new feature to persist editor UI preferences between page loads and browsers.

* Adds a new preferences persistence API.
* Saves editor preferences in user meta instead of in browser's local storage.

Why?
Due to the transient nature of browser storage, this persistence is not as sticky as it is expected to be, including: switching browsers (unique storage between browsers), or using private browsing tabs (storage cleared between sessions), or the same user across a network of sites (storage unique by domain).

This is a backport from Gutenberg.[WordPress/gutenberg#39795 See WordPress/gutenberg PR 39795].

Props talldanwp, youknowriad, noisysocks, mamaduka, costdev, ironprogrammer, hellofromTonya.
See #56467.
Built from https://develop.svn.wordpress.org/trunk@54182
@bph bph mentioned this pull request Oct 3, 2022
89 tasks
ootwch pushed a commit to ootwch/wordpress-develop that referenced this pull request Nov 4, 2022
Adds a new feature to persist editor UI preferences between page loads and browsers.

* Adds a new preferences persistence API.
* Saves editor preferences in user meta instead of in browser's local storage.

Why?
Due to the transient nature of browser storage, this persistence is not as sticky as it is expected to be, including: switching browsers (unique storage between browsers), or using private browsing tabs (storage cleared between sessions), or the same user across a network of sites (storage unique by domain).

This is a backport from Gutenberg.[WordPress/gutenberg#39795 See WordPress/gutenberg PR 39795].

Props talldanwp, youknowriad, noisysocks, mamaduka, costdev, ironprogrammer, hellofromTonya.
See #56467.

git-svn-id: https://develop.svn.wordpress.org/trunk@54182 602fd350-edb4-49c9-b593-d223f7449a82
@therealgilles
Copy link

I just filed this bug report. I don't think this code should be making API calls to save user preferences if the user is not logged in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Dev Note Requires a developer note for a major WordPress release cycle [Package] Preferences /packages/preferences [Type] Feature New feature to highlight in changelogs.
Projects
None yet
10 participants