-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Framework: Handle and upgrade deprecated blocks #3665
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3665 +/- ##
==========================================
- Coverage 37.74% 37.47% -0.28%
==========================================
Files 279 277 -2
Lines 6743 6709 -34
Branches 1227 1219 -8
==========================================
- Hits 2545 2514 -31
Misses 3536 3536
+ Partials 662 659 -3
Continue to review full report at Codecov.
|
blocks/api/parser.js
Outdated
@@ -181,6 +181,26 @@ export function createBlockWithFallback( name, innerHTML, attributes ) { | |||
// as invalid, or future serialization attempt results in an error | |||
block.originalContent = innerHTML; | |||
|
|||
// If the block is invalid, try to find an older compatible version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe expand a bit on this comment, as it's important to understand why.
Quick one:
When a block is invalid, attempt to validate again using a supplied `deprecated` definition.
This allows blocks to modify their attribute and markup structure without invalidating
content written in previous formats.
registerBlockType( 'core/quote', { | ||
title: __( 'Quote' ), | ||
icon: 'format-quote', | ||
category: 'common', | ||
|
||
attributes: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why move this away?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why move this away?
To address this, it's because the deprecated and original versions share most of the same attributes, with deprecated assigning into to reflect the original attributes behavior of citation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have concerns with block looking different at first sight when you open one to learn from, that i think we should minimize.
blocks/library/quote/index.js
Outdated
) } | ||
</blockquote> | ||
); | ||
}, | ||
|
||
deprecatedVersions: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about just deprecated
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me
blocks/library/quote/index.js
Outdated
deprecatedVersions: [ | ||
{ | ||
attributes: { | ||
...blockAttributes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. This is the kind of thing I think we should standardize. :)
}, | ||
}, | ||
|
||
save( { attributes } ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a slight concern with accumulating save
mechanisms just for validation, but I don't see an alternative. Might be worth moving all the deprecated definitions into a deprecated.js
file to not burden the main js with additional save definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the "attributes"? do you think we should share common attributes or maybe fine to duplicate?
I like this approach a lot more. |
39583ed
to
37de716
Compare
@youknowriad another side thing, I'd like to use this opportunity to change the class names used for the quote styles to be |
@mtias Yep, sounds like a good idea. The |
I'd be ok with introducing here if it serves a good testing purpose. |
@@ -238,7 +240,7 @@ registerBlockType( 'core/quote', { | |||
|
|||
return ( | |||
<blockquote | |||
className={ `blocks-quote-style-${ style }` } | |||
className={ style === 2 ? 'is-large' : '' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could use default
and large
for the attribute itself? Or undefined
and large
maybe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmm, this means the value of the attribute can change between two versions, I'm going to introduce a migrate
function to handle this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm, can it be handled in the attribute itself as a fallback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to sit on it a bit otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we can't :(. Adding it is not so complex though 5dd81ce
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's extract that commit into a separate PR so we can discuss the migration of values in isolation.
blocks/api/parser.js
Outdated
let attributesParsedWithDeprecatedVersion; | ||
const hasValidOlderVersion = find( blockType.deprecated, ( oldBlockType ) => { | ||
const deprecatedBlockType = { | ||
...omit( blockType, [ 'attributes', 'save', 'supports' ] ), // Parsing/Serialization properties |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to omit
here, or is it enough that the spread will override those properties?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we do because some of these properties are optional and we do not want to copy them in the old block's definition.
blocks/api/parser.js
Outdated
@@ -181,6 +181,28 @@ export function createBlockWithFallback( name, innerHTML, attributes ) { | |||
// as invalid, or future serialization attempt results in an error | |||
block.originalContent = innerHTML; | |||
|
|||
// When a block is invalid, attempt to validate again using a supplied `deprecated` definition. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we extract this logic to a separate function, or would it be possible to take advantage of recursion here in returning via createBlockWithFallback
using the deprecated block attempt attributes in place of the original?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion Andrew, the code is better now. I preferred to avoid recursion because the logic is a bit simpler than createBlockWithFallback
and it may include a specific "migrate" call in the future #3673
blocks/api/parser.js
Outdated
// content written in previous formats. | ||
if ( ! block.isValid && blockType.deprecated ) { | ||
let attributesParsedWithDeprecatedVersion; | ||
const hasValidOlderVersion = find( blockType.deprecated, ( oldBlockType ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array#some
(or Lodash's equivalent) would be a more appropriate method here, since we don't care about the found value, just that it exists.
blocks/api/parser.js
Outdated
}; | ||
const deprecatedBlockAttributes = getBlockAttributes( deprecatedBlockType, innerHTML, attributes ); | ||
const isValid = isValidBlock( innerHTML, deprecatedBlockType, deprecatedBlockAttributes ); | ||
attributesParsedWithDeprecatedVersion = isValid ? deprecatedBlockAttributes : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we did extract this to a separate method, I could see this working well as an early return from a loop:
for ( let i = 0; i < deprecated.length; i++ ) {
// ...
if ( isValid ) {
return deprecatedBlockAttributes;
}
}
9240a66
to
e6393ac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good to me.
Two things I noted in testing:
- The validation error for the original attempt shows even when there is a deprecated version available. This might be okay.
- I added a
{"style":2}
block topost-content.js
and it didn't upgrade to apply theis-large
class name.
Try:
'<!-- wp:quote {"style":2} -->',
'<blockquote class="wp-block-quote blocks-quote-style-2"><p>The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.</p><footer>Matt Mullenweg, 2017</footer></blockquote>',
'<!-- /wp:quote -->',
@@ -15,6 +16,15 @@ import { | |||
setUnknownTypeHandlerName, | |||
} from '../registration'; | |||
|
|||
const expectFailingBlockValidation = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: I had a similar need for validation tests, wondering if we ought to put the effort into a generalized "expect console errors" assertion:
gutenberg/blocks/api/test/validation.js
Lines 33 to 43 in 9bfd837
/* eslint-disable no-console */ | |
function expectError() { | |
expect( console.error ).toHaveBeenCalled(); | |
console.error.mockClear(); | |
} | |
function expectWarning() { | |
expect( console.warn ).toHaveBeenCalled(); | |
console.warn.mockClear(); | |
} | |
/* eslint-enable no-console */ |
Maybe expect( console ).toHaveLogged( 'error' );
set up in test/unit/setup-test-framework.js
?
https://facebook.github.io/jest/docs/en/expect.html#expectextendmatchers
cc @gziolo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice idea! We are spying already on console warn and error methods: https://github.com/WordPress/gutenberg/blob/master/test/unit/setup-test-framework.js#L25. It should be easy to inplement 👍
blocks/api/test/parser.js
Outdated
selector: 'div', | ||
}, | ||
}, | ||
save: ( { attributes } ) => <div>{attributes.fruit}</div>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, we have this rule, but it is not capturing this:
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
We should seek to include whitespace inside the curly for these cases for consistency with similar rules.
registerBlockType( 'core/quote', { | ||
title: __( 'Quote' ), | ||
icon: 'format-quote', | ||
category: 'common', | ||
|
||
attributes: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why move this away?
To address this, it's because the deprecated and original versions share most of the same attributes, with deprecated assigning into to reflect the original attributes behavior of citation.
e6393ac
to
5a1ef20
Compare
It did upgrade for me with this exact same code, what am I missing here? |
You mean the visual dialog prompting the user or in console? |
5a1ef20
to
bd6f99c
Compare
I'm merging this one to include it in the release. It's working for me, can't reproduce the failing use-case |
I can't reproduce my originally reported issue on master. Maybe a fluke 👍 |
refs #2541 alternative to #3327
Instead of adding a "version" to the block comment and upgrading blocks version by version, this uses a simpler alternative where:
save
,attributes
andsupport
)Testing instructions
cite
and you can still edit the block as expected in the visual editor