Problem/Motivation

Currently LB has a @todo, which when resolved would introduce dataloss and integrity issues with content revisions:

      if ($entity instanceof RevisionableInterface) {
        // If the parent entity will have a new revision create a new revision
        // of the block.
        // @todo Currently revisions are never created for the parent entity.
        //   This will be fixed in https://www.drupal.org/node/2937199.
        //   To work around this always make a revision when the parent entity
        //   is an instance of RevisionableInterface. After the issue is fixed
        //   only create a new revision if '$entity->isNewRevision()'.
        $new_revision = TRUE;
      }

Work is already underway to fix that in #2937199: Track Layout override revisions on entities which support revisioning. The patch that resolves the @todo goes completely green, despite the actual solution being problematic.

Proposed resolution

Resolve the @todo by removing it and proving with tests why it's actually a required component of the current revisioning model.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Sam152 created an issue. See original summary.

Sam152’s picture

Issue summary: View changes
Sam152’s picture

Issue summary: View changes
Sam152’s picture

Status: Active » Needs review
Issue tags: +Blocks-Layouts
FileSize
4.37 KB
7.8 KB

Here is an integration test. All it does in LB core is make the change proposed in this issue and rename some of the params to better indicate what they are actually doing.

The fail patch is the same test, but with the @todo resolved in as proposed by the todo and the implementation in #2937199: Track Layout override revisions on entities which support revisioning.

I would like to resolve this and agree on the model of the revisions before fixing #3053881: Reverting entity revisions that contain custom blocks erroneously triggers EntityChangedConstraint, since the proposed resolution there depends on these semantics to function, so I have made this test coverage a blocker for that issue.

It may also be worth some lower level integration testing to assert revision IDs instead of content as well.

The last submitted patch, 4: 3053887-4-FAIL.patch, failed testing. View results

tedbow’s picture

This looks good. I think I realized we couldn't not make new revision at some point but forgot to remove this.

the only case where we could not make a new revision is when there aren't any previous revisions of the layout entity. Actually I guess if there no revisions where the layout field is not null. Not sure if make sense to check that. But if you have an entity that supports revision but you don't ever create new revisions then if you make 100 new changes to the layout(no revisions) we still make 100 revisions of the inline block. But the 99 revisions wouldn't be reference anywhere.

johnwebdev’s picture

But if you have an entity that supports revision but you don't ever create new revisions then if you make 100 new changes to the layout(no revisions) we still make 100 revisions of the inline block. But the 99 revisions wouldn't be reference anywhere.

Correct, but to assert that we would need to look at all revisions , to ensure none of them has that revision ID, otherwise we would introduce a data loss.

tedbow’s picture

Correct, but to assert that we would need to look at all revisions , to ensure none of them has that revision ID, otherwise we would introduce a data loss.

Well if we only to cover the case where there NO revisions with any layout data we just have to use and entity query to make sure there are no other revision where the layout field is not NULL. We don't actually have to look at the field value in that case.

Sam152’s picture

Do you think that belongs in a follow-up, since it's adding new behavior?

+++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
@@ -177,20 +177,12 @@ public function handlePreSave(EntityInterface $entity) {
+      $new_revision_if_modified = $entity instanceof RevisionableInterface;
...
+        $this->saveInlineBlockComponent($entity, $component, $new_revision_if_modified, $duplicate_blocks);

@@ -265,16 +257,16 @@ protected function getBlockIdsForRevisionIds(array $revision_ids) {
+  protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision_if_modified, $duplicate_blocks) {
...
+    $plugin->saveBlockContent($new_revision_if_modified, $duplicate_blocks);

+++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
@@ -267,12 +267,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
+   * @param bool $new_revision_if_modified
+   *   Whether to create new revision, if the block was modified.
...
+  public function saveBlockContent($new_revision_if_modified = FALSE, $duplicate_block = FALSE) {

@@ -287,8 +287,10 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
     if ($block) {
-      if ($new_revision) {
+      if ($new_revision_if_modified) {
         $block->setNewRevision();
       }
       $block->save();

Also, do you think it's worth trying to formalise this a bit more? Right now "modified" depends on if a block was originally serialized or not. I also don't know if there is any coverage around the converse of this, that is what happens when a block is skipped because it wasn't originally serialized.

tedbow’s picture

Do you think that belongs in a follow-up, since it's adding new behavior?

Sure. Created #3054886: Do not create new revisions for inline blocks when there are no previous revisions

We could add an assertion to \Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTest::testInlineBlocksRevisioning() that when a block is not edited(it won't be serialized) then a new revision is not created.

Sam152’s picture

I've added the assertions to testInlineBlocksRevisioningIntegrity, since this tests the scenario of updating a layout with multiple blocks, where only one has an actual content change.

Sam152’s picture

Hey @tedbow, did you have any other feedback or concerns with the coverage and approach here? Keen to move this forward as it's blocking the revert issue which is blocking some significant integrity and usability on a project I'm working on.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

tim.plunkett’s picture

Bump

lpeabody’s picture

Bump again. Not being able to revert is causing some major pains for clients.

Poindexterous’s picture

Bump, the revert issue is effecting me also.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

tim.plunkett’s picture

Assigned: Unassigned » tedbow

Assigning to @tedbow per #12

tedbow’s picture

Assigned: tedbow » Unassigned
Status: Needs review » Needs work

@Sam152 sorry it took me so long to get to this.
Just a couple of

  1. +++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
    @@ -287,8 +287,10 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
    +    // Since the custom block is only set if it was unserialized, the flag will
    +    // only effect blocks which were modified or serialized originally.
         if ($block) {
    

    I think this comment should be moved inside the if statement. Otherwise it seems to ignore the case where $block is set because of the duplicate block logic above this.

  2. +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php
    @@ -258,6 +258,94 @@ public function testInlineBlocksRevisioning() {
    +      'administer node fields',
    +      'administer nodes',
    

    For this test the user doesn't actually edit the node only the layout. So they don't need these permission.

    Wonder if this would be more clear if we used 'access content' and 'view all revisions' instead.

Deepak Goyal’s picture

Assigned: Unassigned » Deepak Goyal
Sam152’s picture

Assigned: Deepak Goyal » Unassigned
Status: Needs work » Needs review
FileSize
1.53 KB
8.66 KB

Please don't assign issues unless you intend on working on them.

Thanks for the review @tedbow, those changes seem reasonable.

tedbow’s picture

+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php
@@ -263,6 +263,93 @@ public function testInlineBlocksRevisioning() {
+      'bypass node access',

Sorry my dreditor comment was off we don't need 'bypass node access' can be replace with 'access content'

Otherwise looks good 👍

tedbow’s picture

Status: Needs review » Needs work
Lal_’s picture

Status: Needs work » Needs review
FileSize
8.66 KB
3.94 KB
Lal_’s picture

FileSize
586 bytes

sorry wrong interdiff

tedbow’s picture

Status: Needs review » Reviewed & tested by the community

This looks good. Thanks for the patch @Lal_
Thanks for all the work @Sam152!

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Bug Smash Initiative
  1. +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
    @@ -163,20 +163,12 @@ public function handlePreSave(EntityInterface $entity) {
    +      // Since multiple parent entity revisions may reference common block
    +      // revisions, when a block is modified, it must always result in the
    +      // creation of a new block revision.
    +      $new_revision_if_modified = $entity instanceof RevisionableInterface;
           foreach ($this->getInlineBlockComponents($sections) as $component) {
    -        $this->saveInlineBlockComponent($entity, $component, $new_revision, $duplicate_blocks);
    

    can we just inline this while we're here?

    ie

    $this->saveInlineBlockComponent($entity, $component, $entity instanceof RevisionableInterface, $duplicate_blocks);
    
  2. +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
    @@ -251,16 +243,16 @@ protected function getBlockIdsForRevisionIds(array $revision_ids) {
    -   * @param bool $new_revision
    -   *   Whether a new revision of the block should be created.
    +   * @param bool $new_revision_if_modified
    +   *   Whether a new revision of the block should be created when modified.
    ...
    -  protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision, $duplicate_blocks) {
    +  protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision_if_modified, $duplicate_blocks) {
    ...
    -    $plugin->saveBlockContent($new_revision, $duplicate_blocks);
    +    $plugin->saveBlockContent($new_revision_if_modified, $duplicate_blocks);
    

    This change of variable name looks out of scope - is there a reason we're doing so here? Can we just improve the doc-block to make it clear that it's 'if modified'?

  3. +++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
    @@ -262,12 +262,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    -   * @param bool $new_revision
    -   *   Whether to create new revision.
    +   * @param bool $new_revision_if_modified
    +   *   Whether to create new revision, if the block was modified.
    ...
    -  public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE) {
    +  public function saveBlockContent($new_revision_if_modified = FALSE, $duplicate_block = FALSE) {
    
    @@ -283,7 +283,9 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
    -      if ($new_revision) {
    ...
    +      if ($new_revision_if_modified) {
    

    same here

  4. +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php
    @@ -263,6 +263,93 @@ public function testInlineBlocksRevisioning() {
    +    $this->assertNodeRevisionContent(4, ['Block 1 updated', 'Block 2 updated']);
    ...
    +    $this->assertNodeRevisionContent(4, ['Block 1 updated', 'Block 2 updated']);
    

    We assert this twice without any changes? Is one a duplicate?

acbramley’s picture

Fixes #27, agreed that renaming the vars is not necessary.

tedbow’s picture

Status: Needs review » Needs work

@acbramley thanks for the patch.

re #28

  1. Inlining this and not have the comment before assigning $new_revision_if_modified actually makes the comment above it confusing. The specifically refers to the what we are sending to the vrar we will send to \Drupal\layout_builder\InlineBlockEntityOperations::saveInlineBlockComponent() for the $new_revision argument.

    but now the comment is before the loop and it is not clear what is referring to.

    Also won't this be less performant anyways to do the $entity instanceof RevisionableInterface test multiple times instead of just once. We don't know how many will be returned from $this->getInlineBlockComponents($sections)
    But performance is probably a small issue. I think having it separate for the comment is more important.

    If others don't think the comment is now confusing thats fine.

Other than that looks good

fenstrat’s picture

Status: Needs work » Needs review
FileSize
7.02 KB
1020 bytes

Fixes #29. Agreed with @tedbow that inlining that argument doesn't make sense given the comment above specifically relates to it, and it avoids reevaluating it inside the foreach.

tedbow’s picture

Status: Needs review » Reviewed & tested by the community

@fenstrat thanks for the update. RTBC🎉

  • larowlan committed 109fe60 on 9.1.x
    Issue #3053887 by Sam152, Lal_, fenstrat, acbramley, tedbow, larowlan:...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed 109fe60 and pushed to 9.1.x. Thanks!

Thanks all for seeing this one through

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.