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

Block Validation, Deprecation and Migration Experience #7604

Open
mtias opened this issue Jun 28, 2018 · 60 comments
Open

Block Validation, Deprecation and Migration Experience #7604

mtias opened this issue Jun 28, 2018 · 60 comments
Assignees
Labels
[Feature] Blocks Overall functionality of blocks [Feature] Raw Handling Related to the ability to convert content to blocks, paste handling, etc [Feature] Writing Flow Block selection, navigation, splitting, merging, deletion... Framework Issues related to broader framework topics, especially as it relates to javascript [Type] Discussion For issues that are high-level and not yet ready to implement. [Type] Overview Comprehensive, high level view of an area of focus often with multiple tracking issues
Projects

Comments

@mtias
Copy link
Member

mtias commented Jun 28, 2018

image

Individual blocks have a strict shapes of markup and attributes that are expected to consider a block valid within the editor. The way this works, at a fundamental level, is that Gutenberg compares the source given for a ny block with what would be the output if that source is run through the block implementation for that block type (attribute sourcing and save). When there are differences here, we show the already infamous dialog.

It's important to draw a distinction between the validation mechanism and what we choose to do with it (the user experience). For now, it's been incredibly useful to have a very strict validation experience where any detected change is considered an external modification.

The user experience goal, though, should be to avoid bothering users and avoid losing important content. From this, the potential tension is already obvious.

It's also crucial that the validation mechanism remains as simple as possible because it'd have performance costs — it is run for every block to determine validity upfront. So I'm not willing to add overhead there. That said, once we detect a block as initially invalid, that's where we have more room for establishing conditions:

  • We could set up a threshold of characters and be ok with overwriting if it's below it (say 30 characters).
  • We could have a list of elements we allow to be different (attributes like classes).
  • We could overwrite and offer a way back instead of showing a dialog.

These could also be combined for more sophisticated experiences, like attempting our best guess and offer a notice that a block has changed, letting people review the changes if something doesn't seem right. This could also be tied with post revisions. That is, if there's revisions support, and we can save one, we can be more aggressive in making a choice for the user. However, if revisions are not enabled or possible, we might want to be more conservative.

There's also multiple conversion choices we have already implemented:

  • Overwrite: runs the block through save and discards what doesn't match.
  • Convert to HTML: transforms the original block into an HTML block.
  • Convert to Blocks: takes the HTML source and runs it through the raw handling mechanism to infer blocks again.

All of these could be the right choice under certain circumstances, but it's harder to tell which should be the default one. It's important to offer all of these (maybe in an ellipsis menu on the dialog), and be very clear about the wording (the "Edit as HTML" is confusing, for example, since it converts the block).

Also, if these changes are being caused by switching to the HTML editor, we might want to act differently and word things differently — example, don't use "This has been edited externally" and maybe the HTML option becomes "Keep as HTML" in that case.


Previewing

We currently show a dimmed out preview of the block current state. It'd be nice to allow users to check a before / after for the different transformations so they can make the best decision visually. This could also be the basis for a better revisions experience by rendering the different states of the block.


Mockups

Here are some quick drafts as to how the process could look. The specific diffing interface might need refinement as we continue to explore, as well as the phrasings of the various options.

Invalid block message:

invalid

Contrary to now, the message now sits in a box that's the same height of a default paragraph (56px), and a gray scrim extends the height of the block.

Block conversion options:

invalid options

Diffing interface:

diff

Invalidation

Is the process with which a block source is compared with its output before the user interacts with a block. When this fails, for whatever reason, the block is considered invalid. This has been an extremely useful mechanism during the development process, highlighting issues with blocks, plugin compatibilities, and so on.

It's important to clarify that this is not a case of whether the markup is "valid" in terms of being HTML spec-compliant but about how the editor knows to create such markup and that its inability to create an identical result can be a strong indicator of potential data loss (the invalidation is then a protective measure).

It goes without saying that the general expectation for the user experience is that invalidation doesn't happen, and when it does, that it minimizes the amount of user intervention needed. However, considering an invalidation does occur, there are a few cases that need to be separated:

  • Things that should not invalidate a block in the first place (HTML attributes like classes or ids or even data-attributes). Even if they were to be discarded after a save cycle.
  • Things that cannot be reconciled and need a decision (like adding an entirely new paragraph between a figure and img tags within an Image block) given the potential for losing content.
  • The user experience of handling conflicts.

The invalidation process can also be deconstructed in phases:

  1. Validate the block exists.
  2. Validate source matches output.
  3. Validate source matches deprecated outputs.
  4. Validate significance of differences.

These are stacked in a way that favors performance and optimizes for the majority of cases. That is to say, the evaluation logic can become more sophisticated the further down it goes in the process. The first few checks have to be extremely efficient since they will be run for all valid blocks. However, once a block is detected as invalid — failing the three first steps — it is alright to spend more time determining validity before falling back to the user's decision.

Validate significance of differences

This is the area that could use improvements going forwards. Most of the currently reported issues come from differences that should not be significant yet produce an invalidation. There are generally two ways to approach this:

  • Revise block saving to allow for these differences (like HTML attributes).
  • Overwrite differences that fall under a threshold as insignificant.

Related issues:

There are also intricacies that surface once blocks are extended.

Validation based on attributes

It has been proposed in several issues that the validation should be based on the attributes instead of the markup but since the blocks are persisted as markup, this is not something that can be actionable at the moment.

Transformations

Another case of data transformation is present in the mechanism for switching a block to another block type. Transforming a block into another block can be destructive, depending on the heuristics established by the two blocks, the source and the destination. Block transformations also come in two shapes:

  • Registering a transformation for a block into another.
  • Using raw-handling / pasting for conversion.

The first case knows about the block's attributes and is the one used in the main block transformation menu. It allows the most knowledge-transfer in the mapping of attributes. Issues in this conversion should be assigned to the individual blocks responsible for it (example, mapping a quote's cite to a plain paragraph).

The second case is used for extracting blocks out of a Classic block, or converting an HTML block into validated core blocks.

This process is grounded on the same handlers for pasting, which is why in general it removes elements as part of its cleanup process — #6102 —. The intention behind pasting is to clean-up the source without losing meaningful information. However, it could be assumed that given an existing chunk of Classic content the editor could be more lenient in the conversion. One way of handling this is separating both operations, pasting and raw conversion: #6878. Another possibility is to alert the user when something is removed.

Related issues:

Potential Tasks

The aim of this issue is to provide enough context for all these related problems so that any improvements can be discussed holistically. Some examples:

  • Capture unexpected top-level attributes and re-apply them without causing an invalidation.
  • Distinguish between pasting and raw conversion so that different elements can be preserved.
  • Use a visual diff check after a source invalidation.
  • Improve the user experience of handling conflicts: Block Validation, Deprecation and Migration Experience #7604
  • Avoid showing the errors in the console when the block is upgraded.
@mtias mtias added Framework Issues related to broader framework topics, especially as it relates to javascript [Feature] Blocks Overall functionality of blocks Needs Design Needs design efforts. [Feature] Raw Handling Related to the ability to convert content to blocks, paste handling, etc [Feature] Writing Flow Block selection, navigation, splitting, merging, deletion... labels Jun 28, 2018
@ZebulanStanphill
Copy link
Member

Related: #5233.

@johngodley
Copy link
Contributor

There seems to be two main parts here:

  • Improve the UI with better choices
  • Improve the validation so we can make decisions for the user and reduce interruption

I think these can be done sequentially, depending how far we want to go.

Improve the UI

The suggested mockups seem a good way of improving the current setup, and breaks down into nice chunks which I’ll split off from this issue:

  • Change block warning to match mocked style
  • Improve text
  • Add an ellipsis menu to the block warning where actions can be added
  • Include additional actions (convert to classic blocks, apply copy/paste cleanup, compare conversion etc)

The ‘compare conversion’ ability seems a good thing to have in all situations - whatever my choice I’d like to know the result.

Also, improving the text will be a big help here. As a new Gutenberg user the current message is an almost instant ‘panic’ situation that makes me want to switch back to the classic editor where it's not going to scare me!

Improve the validation

offer a notice that a block has changed, letting people review the changes if something doesn't seem right

Forgive my lack of design skills or knowledge of Gutenberg’s design language - maybe this already exists - but something like this?

a4f8126f-0a94-4679-af27-ad3ff2e002da

That is, some kind of small flag to indicate something happened, but it’s probably ok to ignore. Clicking it could show a reason with options to view the change and take other actions:

5f43b23f-eb92-4baa-a6e6-cfcf8986910e

It can be ignored without getting in the way of editing, and could disappear at some point as an implicit acknowledgement.

@mathetos
Copy link

mathetos commented Jul 3, 2018

@johngodley said:

There seems to be two main parts here:

  • Improve the UI with better choices
  • Improve the validation so we can make decisions for the user and reduce interruption

I'd add a third:

  • Ensure that externally modified blocks can still remain "portable"

Here's an example:

Let's say I create a really great layout. I want to share it with others. But they don't have the media files I have and even if they did, they'd not be in a predictable place. So, I switch to code view in Gutenberg and copy the whole thing in the hopes that when they paste my whole layout into theirs it will pull the images from my site.

But currently, if I'm using a full-width block, when Gutenberg notices that the block is modified both the "Convert to Blocks" and "Edit as HTML" options prevent me from using the full-width feature of the blocks. The best example of this problem is with the cover image block.

@jasmussen
Copy link
Contributor

Forgive my lack of design skills or knowledge of Gutenberg’s design language - maybe this already exists - but something like this?

To expand on this, you're suggesting that we have two levels of validation. One where there's a big or breaking change, that gets the modal notice. The other where we think the conversion is trivial and small, like changing whitespace adding a closing </p> tag or other small changes, and then go ahead and perform the conversion without asking, but notify the user so they can undo it if they like — is that correct? If yes, then I love it.

To polish your mockup a little bit, here's a different version:

screen shot 2018-07-06 at 10 43 51

It's an exclamation mark in a circle, below the ellipsis. It's blue to ensure contrast, and because it's supposedly a small change it indicates.

Let's also open the popout as an overlay of the block itself, i.e. down and left, so we don't have any responsive issues:

screen shot 2018-07-06 at 10 44 14

@johngodley
Copy link
Contributor

@mathetos I'm not sure I fully understand. You are copy/pasting a post (or block?) HTML to another site and a block on the other site is showing the warning?

@Alx101
Copy link

Alx101 commented Jul 10, 2018

I feel like what @mathetos could work, but perhaps even expanded to provide developers with the option to create "porting" functions for the block.
Many developers do running upgrades to their theme (at least I do, improve template files, new layouts, etc.), so changing how the markup of the block will look like is something that is highly likely a developer would have to do, even on a production site.

Being forced to convert to html or remove the block entirely is not an option when the whole site is using the block that you changed.

Either just giving a soft warning about block changes (meaning it doesn't force you to convert the block) or providing developers with an API to write "upgrade functions" that transform an older version of the block to a newer version is necessary for anyone who's gonna be creating and upgrading custom blocks.

@skorasaurus
Copy link
Member

skorasaurus commented Jul 18, 2018

As a person new to editing and creating blocks; I'm still unsure of a proper workflow to ensure changes that I make to my blocks' source code (in a separate code editor, say sublimetext) are displayed as I intended in a post that contains the block. With this in mind, I'd tinker with the styling and content in the save button, add some new functionality, and then when I'd refresh the page containing that block; it would display.

I think this feedback loop is very important and helpful to people new to creating gutenberg blocks.

The author of #6826 also had the same concern and I'd love to hear your recommendations for handling this; workflow for rapidly viewing your changes you make to your block's source code.

I see the tension in this; because if a plugin developer updates a block (changing a default class to be 20 px instead of 10px) and a less tech-experienced user (who never creates their own blocks) updates the plugin's block; the user's content could display remarkably differently and the user won't know why.

@DannyCooper
Copy link
Contributor

I've noticed this happens even on the paragraph block. If you use the InspectorControls panel to set a custom font size of 30px the HTML looks like this:

<p style="font-size:30px">This is text</p>

If you change only 30 to 20 in the 'Edit as HTML' view then you get the modified externally error. As the structure has stayed exactly the same with only one value changing it seems like Gutenberg should be able to handle the change.

@TheAggressive
Copy link

TheAggressive commented Nov 22, 2019

Is there any update to this concern? I don’t really understand the need to save legacy code after the code base has been updated to newer structure. Then write deprecated logic to manually go to each post and update to reflect new structure. As well as maintain older styles to go along with legacy code.

What’s the use case? If you want people to use less dynamic blocks as it’s encouraged in the Gutenberg handbook, it has to be easier to make html structure edits without jumping through hurdles and keeping legacy code from making it to the end user and audience. Is there something we are missing? Examples of does and don’t when coding our save outputs?

I’ve just been reading through all these issues the last couple days and the defense’s just don’t work and an explanation of the logic behind the decision would really help everyone. If a contributor can explain.

@bfintal
Copy link
Contributor

bfintal commented Nov 29, 2019

@TheAggressive The main concern here is about keeping the integrity of blocks across different versions, and being able to correctly parse the attributes from them.

If you have a block wherein the class names and other structural things (or even attribute names) change over the course of the block/plugin being updated to have more fixes or features, the block editor would need a way to decipher what the block's actual attributes are based from a saved version of a block.

For example, if you had created a post with a block a few months ago, and the plugin that supplied that block was updated a few times already, the legacy save code would provide a way to make sense of your old saved block. If the legacy code wasn't there, then there would be a chance that the attributes won't get parsed correctly.

@bfintal
Copy link
Contributor

bfintal commented Nov 29, 2019

I'd like to bring up that reusable blocks with deprecated block versions won't run through the migration process. The problem here is that the reusable blocks don't go through the JS deprecation methods when they're being rendered for the frontend.

The only way to migrate a reusable block with a deprecated block is to manually edit it in the block editor and update it.

Related #9070

@vladoa
Copy link

vladoa commented Dec 13, 2019

So, is there any easy way to just update the markup or add a css class in a custom block without fearing that it might break hundreds of pages?
Writing a deprecation for every little thing is just painful.

@eliot-akira
Copy link

eliot-akira commented Dec 13, 2019

@vladoa From @jodamo5 's comment (quoted below), for now the solution is to use dynamic blocks to avoid validation/deprecation. For reference, here's the Tutorial: Creating Dynamic Blocks.

Key points:

  • Return null for the save function. This stops Gutenberg saving the HTML into the database.
  • Attributes of the blocks are automatically saved to the database when the page is saved. (Stored as HTML comments that Gutenberg reads).
  • We then use these attributes as the data to use when rendering the block on the front end. So the HTML is always built on the fly from our PHP code, which means that any update we make to the block will be immediately replicated across the site.

In my mind this should be the default way blocks are built instead of the current default of using the "save" function which stores the HTML in the database.

@vladoa
Copy link

vladoa commented Dec 13, 2019

@eliot-akira if I use render_callback technique to render with PHP, will I be able to nest my custom block, in let's say, Columns Block?

@MonsterKing
Copy link

MonsterKing commented Dec 13, 2019 via email

@MonsterKing
Copy link

MonsterKing commented Dec 13, 2019 via email

@vladoa
Copy link

vladoa commented Dec 13, 2019

@MonsterKing you are right, I have just tried it out. Works fine. It just still feels weird doing it again in PHP even when React is introduced in the frontend.

@mmtr
Copy link
Contributor

mmtr commented Dec 16, 2019

I like what @gziolo suggested above.

I think that having 2 validation modes as a start could offer more flexibility:

  • Strict: the current one that marks a block as invalid if it is not an exact match.
  • Moderate: allows extra attributes, as long as the ones provided by the save function are not altered (i.e. additional classes, data-* attributes, etc). It could still warns the differences in the browser console, and shows a tiny icon as a UI indicator that displays those differences when hovering it.

The strict mode will be the default one, allowing users to switch to the moderate mode with a new Gutenberg setting in WP Admin and/or developers with a filter.

For technical reference, the current strict mode is implemented in the isEquivalentHTML function.

@gwwar
Copy link
Contributor

gwwar commented Dec 18, 2019

I've added a try PR exploring what adding more block validation modes looks like. In this first pass I've added another option that basically defers to the block, provided that it doesn't throw errors in it's save function. (mimics behavior of "attempt block recovery" without prompting the user)

A more "moderate" mode here is in my opinion both expensive to build and does not clearly benefit end users.

#19188

Let me know what folks think.

@skorasaurus
Copy link
Member

anyone interested in seeing this resolved should checkout what's proposed in #21703

@youknowriad youknowriad added [Type] Discussion For issues that are high-level and not yet ready to implement. and removed [Type] Tracking Issue Tactical breakdown of efforts across the codebase and/or tied to Overview issues. labels Jun 4, 2020
@daledesilva
Copy link

I’ve read this whole thread and feel there’s a lot of good discussion here.
The thoughts about future evolution of how migration and deprecation should work are good to read, however, I think there is a lot of UX improvements that can make the process much smoother as well.

Simple changes in wording, and structure of the options in the dialog, can go a long way. I’ve created another issue with mock-ups and explanations that details my suggestions:
#25436

@mtias
Copy link
Member Author

mtias commented Sep 25, 2020

Yes, this is a larger overview and endeavor that should not block other meaningful improvements to the user experience that can be done separately.

@samuelfarrell
Copy link

samuelfarrell commented Sep 13, 2021

While I understand the benefits of being able to just pull out the markup stored in the post content for display on the front end, trying to parse attributes from that content is just not a winning strategy for the long term. In practice, we end have 10+ versions of some of our blocks, and because it only updates on page edit, we have each and every one of those block deprecations living out in the wild somewhere, which means we have to keep old CSS around that's only used for pages that haven't been touched for 2+ years.

The block editor is a react app, why are we not letting it act like one? The attributes array already provides data structure for all our blocks, why not just save attribute values explicitly in a json array in the db for each post as well as the html content? On update, we can nudge the blocks to update their html and they have a pool of explicit values on which to do so.

@GreenballParis
Copy link

Is there a way to trigger the “Attempt block recovery” action on all our broken blocks sitewide? Waiting since 18 months..

@MonsterKing
Copy link

Is there a way to trigger the “Attempt block recovery” action on all our broken blocks sitewide? Waiting since 18 months..

Not that I know of. I wrote a little script that looked for the problem content in the database and removed it. So, with my client, there is a button to click that cleans everything up (as long as it's the same problem). We haven't had to use it in a while. Not sure if it still works. But, if you look at (and copy and paste it in a text editor) the wp_content field in the (prefix)posts table for your post before you attempt a restore and then look again after restoration (copy and paste again) and compare the two, you'll see what shenanigans in the "bad" version were changed. That's what I did. It was just some weird "placeholder" thing that was being added. Once I stripped that stuff out, all was fine again. I wrote a function that did the work for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Blocks Overall functionality of blocks [Feature] Raw Handling Related to the ability to convert content to blocks, paste handling, etc [Feature] Writing Flow Block selection, navigation, splitting, merging, deletion... Framework Issues related to broader framework topics, especially as it relates to javascript [Type] Discussion For issues that are high-level and not yet ready to implement. [Type] Overview Comprehensive, high level view of an area of focus often with multiple tracking issues
Projects
Overviews
Infrastructure
Development

No branches or pull requests