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

Expand on the deprecations documentation #27286

Merged
merged 5 commits into from
Nov 27, 2020
Merged

Conversation

glendaviesnz
Copy link
Contributor

Description

How has this been tested?

Screenshots

Types of changes

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • 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.

@glendaviesnz glendaviesnz self-assigned this Nov 26, 2020
@github-actions
Copy link

github-actions bot commented Nov 26, 2020

Size Change: +71 B (0%)

Total Size: 1.2 MB

Filename Size Change
build/block-directory/index.js 8.72 kB +7 B (0%)
build/block-editor/index.js 134 kB +40 B (0%)
build/block-library/editor.css 8.96 kB +3 B (0%)
build/block-library/index.js 148 kB +24 B (0%)
build/block-library/style-rtl.css 8.26 kB +36 B (0%)
build/block-library/style.css 8.26 kB +37 B (0%)
build/block-library/theme-rtl.css 789 B -3 B (0%)
build/block-library/theme.css 790 B -3 B (0%)
build/blocks/index.js 48.1 kB +2 B (0%)
build/components/index.js 172 kB -7 B (0%)
build/core-data/index.js 14.8 kB +2 B (0%)
build/data-controls/index.js 828 B +1 B
build/data/index.js 8.8 kB +4 B (0%)
build/date/index.js 11.2 kB -1 B
build/edit-navigation/index.js 11.1 kB +7 B (0%)
build/edit-post/index.js 306 kB -81 B (0%)
build/edit-site/index.js 24 kB -9 B (0%)
build/edit-widgets/index.js 26.3 kB +2 B (0%)
build/editor/index.js 43.3 kB +2 B (0%)
build/format-library/index.js 6.87 kB +3 B (0%)
build/list-reusable-blocks/index.js 3.1 kB +1 B
build/media-utils/index.js 5.32 kB +1 B
build/redux-routine/index.js 2.84 kB +1 B
build/server-side-render/index.js 2.77 kB +1 B
build/url/index.js 4.06 kB +1 B
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.8 kB 0 B
build/api-fetch/index.js 3.42 kB 0 B
build/autop/index.js 2.84 kB 0 B
build/blob/index.js 664 B 0 B
build/block-directory/style-rtl.css 943 B 0 B
build/block-directory/style.css 942 B 0 B
build/block-editor/style-rtl.css 11.3 kB 0 B
build/block-editor/style.css 11.3 kB 0 B
build/block-library/editor-rtl.css 8.95 kB 0 B
build/block-serialization-default-parser/index.js 1.87 kB 0 B
build/block-serialization-spec-parser/index.js 3.06 kB 0 B
build/components/style-rtl.css 15.3 kB 0 B
build/components/style.css 15.3 kB 0 B
build/compose/index.js 9.95 kB 0 B
build/deprecated/index.js 768 B 0 B
build/dom-ready/index.js 571 B 0 B
build/dom/index.js 4.95 kB 0 B
build/edit-navigation/style-rtl.css 881 B 0 B
build/edit-navigation/style.css 885 B 0 B
build/edit-post/style-rtl.css 6.42 kB 0 B
build/edit-post/style.css 6.4 kB 0 B
build/edit-site/style-rtl.css 3.92 kB 0 B
build/edit-site/style.css 3.92 kB 0 B
build/edit-widgets/style-rtl.css 3.13 kB 0 B
build/edit-widgets/style.css 3.13 kB 0 B
build/editor/editor-styles-rtl.css 476 B 0 B
build/editor/editor-styles.css 478 B 0 B
build/editor/style-rtl.css 3.85 kB 0 B
build/editor/style.css 3.85 kB 0 B
build/element/index.js 4.62 kB 0 B
build/escape-html/index.js 735 B 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.27 kB 0 B
build/html-entities/index.js 623 B 0 B
build/i18n/index.js 3.57 kB 0 B
build/is-shallow-equal/index.js 698 B 0 B
build/keyboard-shortcuts/index.js 2.54 kB 0 B
build/keycodes/index.js 1.94 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/notices/index.js 1.81 kB 0 B
build/nux/index.js 3.42 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.43 kB 0 B
build/priority-queue/index.js 790 B 0 B
build/reusable-blocks/index.js 2.92 kB 0 B
build/rich-text/index.js 13.3 kB 0 B
build/shortcode/index.js 1.69 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/viewport/index.js 1.86 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.22 kB 0 B

compressed-size-action


For ease of management it is recommended that you version the deprecations by saving each deprecation into a separate file with the name of the block version it applies to. Then import each of these objects into the `deprecated` property array mentioned below. It is also recommeded to keep [fixtures](https://github.com/WordPress/gutenberg/tree/master/packages/e2e-tests/fixtures/blocks) which contain the different versions of the block content to allow you to easily test that new deprecations and migrations are working across all previous versions of the block.
For ease of management, it is recommended that you version the deprecations by saving each deprecation into a separate file with the name of the block version it applies to. Then import each of these objects into the `deprecated` property array mentioned below. It is also recommended to keep [fixtures](https://github.com/WordPress/gutenberg/tree/master/packages/e2e-tests/fixtures/blocks) which contain the different versions of the block content to allow you to easily test that new deprecations and migrations are working across all previous versions of the block.
Copy link
Contributor

@talldan talldan Nov 26, 2020

Choose a reason for hiding this comment

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

A brief example might be good, as I think it's quite hard to visualize what a 'block version' is. Some might misinterpret this as an explicit version number in the deprecation object, but I think what you're getting at is better semantics using variable names.

I guess this is the only bit of the PR that might need more discussion, as it's not current practice.

I'd be interested in other thoughts. Something I considered once before was to use comments like:

// Initial version of block
// ...
// 1. Deprecation after captions added.

I like the idea of numbering matching the fixtures. Almost makes me think the fixture html file should live near the deprecation file in a subfolder. An advantage of linking them together in some way is that the tests could then be generic deprecation tests that even work on a third party block that's defined the same way.

Could also be nice to link to the readme in the fixtures folder, as they're quite difficult to understand for a newcomer.

Copy link
Contributor Author

@glendaviesnz glendaviesnz Nov 26, 2020

Choose a reason for hiding this comment

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

thanks @talldan, that is a good idea to expand this with an example ... we went with this approach in the gallery refactor - https://github.com/WordPress/gutenberg/pull/25940/files#diff-04bda7ff65177c115240f624f7b131cb24350a290b83d9f16f0462bc8a111132R4 - what do you think about this as a suggested way of handling it? We copied this from the way some of the jetpack blocks handle deprecations, eg. https://github.com/Automattic/jetpack/tree/master/extensions/blocks/opentable/deprecated

Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense to have some way to indicate which deprecation came first. For core blocks I think I've noticed usually the earliest deprecation is last in the array, but as you say, that's not a clear convention for contributors.

I'm not overly opinionated on the method used to improve this. I anticipate some might not like the idea of splitting the deprecations into multiple files, but I personally am ok with that. Having said that, the same system could easily be used within one file. e.g.:

const v1 = { // ... };
const v2 = { // ... };

const deprecated = [ v1, v2 ];
export default deprecated;

Worth pinging others on this like @gziolo who's done loads of good work on improving the block structure and bringing consistency across blocks.

Copy link
Member

Choose a reason for hiding this comment

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

First of all, I'm not aware of any recommendation about using a separate file for each deprecation. I never saw it materialized in the codebase as well. Can you expand on that part?

I'm not overly opinionated on the method used to improve this.

If it you think it makes sense like in the linked PR for Gallery block then it's fine. In other places it might create some challenges how to reuse attributes or migration code. Just saying that it have pros and cons. Sometimes hardcoded data would be better but developers hate that 🤷‍♂️

The order of deprecations is important. New deprecation should be always added as a first item in the array (prepended). In effect, the block editor tries all versions of the block in the historical order, from the recent one to the oldest. In the example shared, it should be:

const v1 = { // ... };
const v2 = { // ... };

const deprecated = [ v2, v1 ];
export default deprecated;

It's also how HTML fixtures are named. Version 1 is always the oldest deprecation.

Almost makes me think the fixture html file should live near the deprecation file in a subfolder. An advantage of linking them together in some way is that the tests could then be generic deprecation tests that even work on a third party block that's defined the same way.

There are fixtures for deprecations and current implementation. Actually both could live in the fixtures folder of the corresponding block. I like this idea. I'm not sure how much work it would be but it's something worth exploring. ❤️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given this feedback I think that keeping in a single file, but assigning each to a const may be an easier step than putting each in its own file, so have reworded and provided and example for this ... and have also refactored the gallery PR to use a single file instead of multiple.

Comment on lines 10 to 12
Deprecations do not operate as a chain of updates in the way other software data updates, like database migrations, do. At first glance, it is easy to think that each deprecation is going to make the required changes to the data and then hand this new form of the block onto the next deprecation to make its changes. What happens is that each deprecation is passed the original saved content, and if its `save` method produces a valid block it is used to parse the block attributes. If it has a `migrate` method it will also be run, and the data is then passed back to the current block `save` function to create the new valid version of the block.

It is also important to note that if a deprecation's `save` method does not produce a valid block then it is skipped, including its `migrate` method, even if `isEligible` returns true. This means that if you have several deprecations for a block and want to perform a new migration, like moving content to InnerBlocks, you may need to include the `migrate` method in multiple deprecations for it to be applied to all previous versions of the block.
Copy link
Contributor

Choose a reason for hiding this comment

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

This and the previous paragraph are really nice additions.

The way migrate works is unfortunate, as central aspects of a block deprecation (attributes and save) don't need to be changed once written, which I think leads to this misunderstanding about migrate, but good job on documenting 👍.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it's confusing but it makes sense, because it uses attributes and save as they existed when the block was saved. migrate transforms parsed attributes to the shape that the most recent save can consume.

@gziolo gziolo added [Type] Developer Documentation Documentation for developers [Feature] Block Validation/Deprecation Handling block validation to determine accuracy and deprecation labels Nov 26, 2020
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

Thank you for trying to clarify how deprecation works. It's definitely one of the most complex and therefore confusing aspect of block parsing 😃

In general you did excellent job filling the gaps. I left some comments where I felt some tweaks might help to increase the chance folks will understand the flow quicker. Although, I'm aware that it's extremely hard to get it all right. I think @youknowriad or @mcsf might find some mistakes in my reasoning 😃

@@ -7,12 +7,18 @@ When updating static blocks markup and attributes, block authors need to conside

A block can have several deprecated versions. A deprecation will be tried if a parsed block appears to be invalid, or if there is a deprecation defined for which its `isEligible` property function returns true.

Deprecations do not operate as a chain of updates in the way other software data updates, like database migrations, do. At first glance, it is easy to think that each deprecation is going to make the required changes to the data and then hand this new form of the block onto the next deprecation to make its changes. What happens is that each deprecation is passed the original saved content, and if its `save` method produces a valid block it is used to parse the block attributes. If it has a `migrate` method it will also be run, and the data is then passed back to the current block `save` function to create the new valid version of the block.
Copy link
Member

Choose a reason for hiding this comment

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

to the current save function to create the valid version of the block

It's very important to make it more clear that migrate takes attributes parsed from the content of saved block, processes them and runs the original (non-deprecated) save func 😃 I found it confusing myself when helping @ntsekouras to improve the logic of deprecation quite recently.

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 have reworded this slightly to try and make it clearer, and also re-ordered things slightly so similar concepts were covered in the same spot

@@ -7,12 +7,18 @@ When updating static blocks markup and attributes, block authors need to conside

A block can have several deprecated versions. A deprecation will be tried if a parsed block appears to be invalid, or if there is a deprecation defined for which its `isEligible` property function returns true.

Deprecations do not operate as a chain of updates in the way other software data updates, like database migrations, do. At first glance, it is easy to think that each deprecation is going to make the required changes to the data and then hand this new form of the block onto the next deprecation to make its changes. What happens is that each deprecation is passed the original saved content, and if its `save` method produces a valid block it is used to parse the block attributes. If it has a `migrate` method it will also be run, and the data is then passed back to the current block `save` function to create the new valid version of the block.

It is also important to note that if a deprecation's `save` method does not produce a valid block then it is skipped, including its `migrate` method, even if `isEligible` returns true. This means that if you have several deprecations for a block and want to perform a new migration, like moving content to InnerBlocks, you may need to include the `migrate` method in multiple deprecations for it to be applied to all previous versions of the block.
Copy link
Member

Choose a reason for hiding this comment

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

The explanation for isEligible might be not 100% correct. This function checks whether the deprecation should be exercised in the first place. In other words, if this function returns false the deprecation gets ignored. @mcsf, is it correct?

There is also another mention about isEligible earlier in the document so it might need some consolidation.

Copy link
Contributor

Choose a reason for hiding this comment

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

my understanding is that the deprecations are run if the current save produces an invalid block, regardless of the isEligible return ... but if the current save is valid then isEligible is called to see if the deprecation should run anyway:

If I understand the getMigratedBlock function correctly, the block.isValid property is only set to true once a deprecation is successfully applied. So it is not, if the "current save" produces an invalid block but rather if after the deprecations processed so far still leave the block in an invalid state.

Your point that isEligible is not executed in every circumstance, is correct. To me the isEligible provides the means to migrate an otherwise valid block.


For ease of management it is recommended that you version the deprecations by saving each deprecation into a separate file with the name of the block version it applies to. Then import each of these objects into the `deprecated` property array mentioned below. It is also recommeded to keep [fixtures](https://github.com/WordPress/gutenberg/tree/master/packages/e2e-tests/fixtures/blocks) which contain the different versions of the block content to allow you to easily test that new deprecations and migrations are working across all previous versions of the block.
For ease of management, it is recommended that you version the deprecations by saving each deprecation into a separate file with the name of the block version it applies to. Then import each of these objects into the `deprecated` property array mentioned below. It is also recommended to keep [fixtures](https://github.com/WordPress/gutenberg/tree/master/packages/e2e-tests/fixtures/blocks) which contain the different versions of the block content to allow you to easily test that new deprecations and migrations are working across all previous versions of the block.
Copy link
Member

Choose a reason for hiding this comment

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

First of all, I'm not aware of any recommendation about using a separate file for each deprecation. I never saw it materialized in the codebase as well. Can you expand on that part?

I'm not overly opinionated on the method used to improve this.

If it you think it makes sense like in the linked PR for Gallery block then it's fine. In other places it might create some challenges how to reuse attributes or migration code. Just saying that it have pros and cons. Sometimes hardcoded data would be better but developers hate that 🤷‍♂️

The order of deprecations is important. New deprecation should be always added as a first item in the array (prepended). In effect, the block editor tries all versions of the block in the historical order, from the recent one to the oldest. In the example shared, it should be:

const v1 = { // ... };
const v2 = { // ... };

const deprecated = [ v2, v1 ];
export default deprecated;

It's also how HTML fixtures are named. Version 1 is always the oldest deprecation.

Almost makes me think the fixture html file should live near the deprecation file in a subfolder. An advantage of linking them together in some way is that the tests could then be generic deprecation tests that even work on a third party block that's defined the same way.

There are fixtures for deprecations and current implementation. Actually both could live in the fixtures folder of the corresponding block. I like this idea. I'm not sure how much work it would be but it's something worth exploring. ❤️

Comment on lines 10 to 12
Deprecations do not operate as a chain of updates in the way other software data updates, like database migrations, do. At first glance, it is easy to think that each deprecation is going to make the required changes to the data and then hand this new form of the block onto the next deprecation to make its changes. What happens is that each deprecation is passed the original saved content, and if its `save` method produces a valid block it is used to parse the block attributes. If it has a `migrate` method it will also be run, and the data is then passed back to the current block `save` function to create the new valid version of the block.

It is also important to note that if a deprecation's `save` method does not produce a valid block then it is skipped, including its `migrate` method, even if `isEligible` returns true. This means that if you have several deprecations for a block and want to perform a new migration, like moving content to InnerBlocks, you may need to include the `migrate` method in multiple deprecations for it to be applied to all previous versions of the block.
Copy link
Member

Choose a reason for hiding this comment

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

Yes, it's confusing but it makes sense, because it uses attributes and save as they existed when the block was saved. migrate transforms parsed attributes to the shape that the most recent save can consume.

Deprecations are defined on a block type as its `deprecated` property, an array of deprecation objects where each object takes the form:

- `attributes` (Object): The [attributes definition](/docs/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block.
- `supports` (Object): The [supports definition](/docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block.
- `save` (Function): The [save implementation](/docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block.
- `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block.
- `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block. As mentioned above, a deprecation's `migrate` will not be run if its `save` function does not return a valid block so you will need to make sure your migrations are available in all the deprecations where they are relevant.
Copy link
Member

Choose a reason for hiding this comment

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

Nice, this is very clear here and in alignment with my previous comments 👍

@aaronrobertshaw
Copy link
Contributor

I've made some updates to improve clarity and fix a few issues where the described deprecation flow did not match the code. This was mostly related to how isEligible is used.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

Thanks for applying further revisions. I'm biased now but obviously happy with the current proposal 😆 You can merge as is or wait for more feedback. Again, very important changes and much appreciated ❤️

@glendaviesnz glendaviesnz marked this pull request as ready for review November 27, 2020 23:50
@glendaviesnz glendaviesnz merged commit ebd26f4 into master Nov 27, 2020
@glendaviesnz glendaviesnz deleted the update/deprecation-docs branch November 27, 2020 23:52
@github-actions github-actions bot added this to the Gutenberg 9.5 milestone Nov 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block Validation/Deprecation Handling block validation to determine accuracy and deprecation [Type] Developer Documentation Documentation for developers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants