Add rudimentary conflict prevention to the Config Auto-save end-point

Created on 16 June 2025, 28 days ago

Overview

As part of ๐Ÿ“Œ META: Conflict free concurrent editing Active we are actually going to make sure we prevent concurrent editing
for layout, page data and regions, we are handling this with ๐Ÿ“Œ Add rudimentary conflict prevention to the preview end-point Active and ๐Ÿ“Œ [PP-1] Enforce conflict enforcement outside of tests and e2e tests Postponed

But the same problem exists for Javascript component and asset libraries.

if users open up the sam config entity in XB they will be overwriting each other's work with every auto-save

Proposed resolution

Return the latest auto-save to the client and do not allow an auto-save if they do not have the lastest version

We may want reload the auto-save version from the server for the global CSS when the user focuses the form because everyone will have the same copy

User interface changes

๐Ÿ› Bug report
Status

Active

Version

0.0

Component

Auto-save

Created by

๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @tedbow
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    Postponing on ๐Ÿ“Œ [PP-1] Enforce conflict enforcement outside of tests and e2e tests Postponed because that issue end up having some back-end changes we would likely use here

  • ๐Ÿ‡ซ๐Ÿ‡ฎFinland lauriii Finland
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States effulgentsia
  • Merge request !1204Resolve #3530258 conflict detection for config โ†’ (Merged) created by tedbow
  • Pipeline finished with Failed
    14 days ago
    Total: 1308s
    #535488
  • Pipeline finished with Failed
    14 days ago
    Total: 1459s
    #535516
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    I brought in some more changes from ๐Ÿ“Œ [PP-1] Enforce conflict enforcement outside of tests and e2e tests Postponed just so it is ready to go once that issue is in. but that issue should still be fixed first

  • Pipeline finished with Failed
    14 days ago
    Total: 1522s
    #535543
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    ๐Ÿ“Œ [PP-1] Enforce conflict enforcement outside of tests and e2e tests Postponed is in๐ŸŽ‰

    Need to bring in the latest changes from 0.x now

  • Pipeline finished with Failed
    12 days ago
    Total: 1598s
    #537373
  • Pipeline finished with Failed
    12 days ago
    Total: 969s
    #537414
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
  • Pipeline finished with Failed
    7 days ago
    Total: 1019s
    #540992
  • Pipeline finished with Success
    7 days ago
    Total: 778s
    #541012
  • Pipeline finished with Success
    7 days ago
    Total: 677s
    #541025
  • Pipeline finished with Failed
    7 days ago
    Total: 943s
    #541157
  • Pipeline finished with Failed
    7 days ago
    Total: 768s
    #541185
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    @jessebaker, this is the MR we chatted about.

    On the client side I used claude code to hopefully handle the auto-save conflict detection in the same way you set up in ๐Ÿ“Œ Add rudimentary conflict prevention to the preview end-point Active . Hopefully this better than the first attempt with claude on the other issue because it was using the existing pattern and creating something from scratch.

    For the `/xb/api/v0/config/auto-save/{entityTypeId}/{configEntityId}` pathes that client calls to save the auto-save for JS Components and the Asset library I have changed the request to nest the current entity data under a new top level `data` key, so that allows a sibling autoSaves like we used in #3490565

    let me know what you think

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    @effulgentsia just raised with @balintbrews what he thought about test coverage would be appropriate for the experience_builder.api.config.auto-save.(get|patch) routes supporting conflict detection/prevention. @balintbrews recommended (and @effulgentsia +1'd) that no end-to-end tests nor functional tests are needed, because it'd require writing all of those from scratch.

    So they recommend: Kernel tests + manual testing of the inability to lose data in the UI.

    IOW: the scope here is ONLY avoiding data loss on back-end, out of scope is informing the user of this.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    re #10

    So they recommend: Kernel tests + manual testing of the inability to lose data in the UI.

    Sounds good. I have manually tested it and it work and it should be easy to add a kernel test like \Drupal\Tests\experience_builder\Kernel\ApiLayoutControllerPostTest::testOutdatedAutoSave

    re

    IOW: the scope here is ONLY avoiding data loss on back-end, out of scope is informing the user of this.

    I understand the first part, I don't understand what "out of scope is informing the user of this." is suppose to mean. Are we just suppose to error in the auto-save but user isn't suppose to know this? Do they just keep on trying to make changes but they never take affect?

    FWIW I implement the same error response we added in ๐Ÿ“Œ [PP-1] Enforce conflict enforcement outside of tests and e2e tests Postponed were we just tell them

    Your latest change was not saved because the content was modified
    elsewhere since you loaded the page. Please refresh your browser to
    receive the latest changes and continue.

    See ui/src/features/editor/ConflictWarning.tsx

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    One thing I noticed is that if I am editing a JS component whenever an auto-save is triggered for the component an auto-save request is also triggered for the global asset library, regardless of the fact that I haven't made any changes to global asset library CSS.

    This will probably mean there would be many more conflicts than if we only triggered an auto-save for the global asset library when it changed in the editor. The chance that another user has edited the same JS Component since my last GET request is a lot less then the chance that another user has edited the global asset library, since there could be many JS components but always only one global asset library which everyone is editing.

    We also might want to look into reloading the global asset library auto-save when the user moves to that editor. I could be editing a JS component for 30 minutes and never touched the global asset library then switch to it. In that 30 minutes any user could editing any other JS component could have edited the global asset library

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    again back to #10 and

    IOW: the scope here is ONLY avoiding data loss on back-end, out of scope is informing the user of this.

    I tested locally reverting show the same error warning on conflict as with the layout and the 409 conflict is just logged to the console. So the user is not informed. The effect is that the user can just keep editing the Javascript because they
    don't know to reload their browser but the Preview on right does update with their changes

    I would argue this actually is data loss because we are letting the user continue spend time on the JS with no indication they should do anything, so they will lose this work. The user would have no way of knowing that their work did not make it to the auto-save

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    Assigning back to myself because I need to make the kernel test I mentioned in #11. I have started on that

    @jessebaker you could still review the front-end changes.

  • First commit to issue fork.
  • Pipeline finished with Failed
    6 days ago
    Total: 195s
    #541945
  • Pipeline finished with Failed
    6 days ago
    Total: 875s
    #542074
  • Pipeline finished with Failed
    6 days ago
    #542090
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    Added a new kernel test

  • Pipeline finished with Failed
    6 days ago
    Total: 725s
    #542138
  • Pipeline finished with Failed
    6 days ago
    #542226
  • Pipeline finished with Failed
    6 days ago
    #542263
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    I closed ๐Ÿ“Œ For config entities add client support conflict detection via the `autoSaves' key Active because we will likely need to do the front-end changes here

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    Assiging back to @jessebaker see my MR comments specifically https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... . 2 of the failing e2e test point to the problem

  • Pipeline finished with Failed
    5 days ago
    #543106
  • Pipeline finished with Running
    5 days ago
    #543145
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    I am confused by the e2e gitlab CI fails.

    Getting errors like

    cy:command โœ˜ uncaught exception Error: {"error":{"status":500,"data":{"message":"Body does not match schema for content-type \"application/json\" for Request [post /xb/api/v0/layout/{entityTypeId}/{entityId}]. [Keyword validation failed: Required property 'autoSaves' must be present in the object in autoSaves]"}},"isUnhandledError":false,"meta":{"request":{},"response":{}}}

    and

    {
    "status": "500",
    "errors": {
    "message": "Body does not match schema for content-type \"application/json\" for Request [patch /xb/api/v0/layout/{entityTypeId}/{entityId}]. [Keyword validation failed: Required property 'autoSaves' must be present in the object in autoSaves]"
    },

    so it appears the client logic to autoSaves to the post and patch requests is not working and the logic that enforces our openapi.yml rule is catching it.

    but when I run these tests locally they do pass. I tested locally altering the openapi.yml file to make sure these e2e will fail for openapi.yml violations and they do locally.

    @jessebaker any possibility the new JS to add autoSaves to the requests somehow is not working on gitlab CI? that is the only thing I can think of

  • First commit to issue fork.
  • Pipeline finished with Success
    4 days ago
    Total: 1818s
    #543812
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    have manually tested this again

    1. create a code component
    2. make an edit <FormattedText>{title} 1</FormattedText>
    3. copy the url and open another browser tab
    4. 2nd tab make an edit <FormattedText>{title} 2</FormattedText>
    5. 1st tab make an edit <FormattedText>{title} 3</FormattedText>
    6. 1st tab errors because it doesn't have the latest changes: this is expected and correct
    7. Refresh browser by clicking on "Refresh" link in error messages
    8. 1st tab show latest changes <FormattedText>{title} 2</FormattedText>
    9. 2nd tab make an edit <FormattedText>{title} 4</FormattedText>
    10. Pause to ensure auto-save
    11. 2nd tab make an edit <FormattedText>{title} 2</FormattedText>: important this brings back auto-save state back the value before the last edit and to the value that tab 1 also has
    12. 1st tab make an edit <FormattedText>{title} 5</FormattedText>
    13. 1st tab does not error: This this is expected and correct, even though the latest save was on tab 2 the 1st tab has code that matches the latest auto-save state, so it should not error
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA
  • Pipeline finished with Failed
    4 days ago
    Total: 717s
    #544068
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    There is one open thread for @jessebaker from @lauriii on the front-end code but I think this is ready for review

  • Pipeline finished with Success
    4 days ago
    Total: 662s
    #544083
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Thanks for the updated issue summary, made it much easier to dive right in!

    Hope my refinements to the issue summary make it easier for the next person.

  • Pipeline finished with Success
    4 days ago
    Total: 1007s
    #544127
  • Pipeline finished with Canceled
    4 days ago
    Total: 377s
    #544167
  • Pipeline finished with Failed
    4 days ago
    Total: 688s
    #544169
  • Pipeline finished with Failed
    4 days ago
    Total: 679s
    #544184
  • Pipeline finished with Failed
    4 days ago
    Total: 736s
    #544247
  • Pipeline finished with Failed
    4 days ago
    Total: 332s
    #544269
  • Pipeline finished with Failed
    4 days ago
    Total: 196s
    #544278
  • Pipeline finished with Failed
    4 days ago
    Total: 764s
    #544285
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Impressive MR!

    2 main concerns:

    1. Cacheability regression: https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...
    2. The clarity and maintainability of both \Drupal\Tests\experience_builder\Kernel\AutoSave\AutoSaveConflictTestTrait::testOutdatedAutoSave() and the code that powers it: \Drupal\experience_builder\Controller\AutoSaveValidateTrait::validateAutoSaves(). Tried to self-address that in https://git.drupalcode.org/project/experience_builder/-/merge_requests/1..., up to @tedbow to review ๐Ÿ‘
  • Pipeline finished with Canceled
    4 days ago
    Total: 314s
    #544335
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Follow-up for #25.1: turns out it's misleading because all of this is in a kernel test. We'll need a stable blocker follow-up to convert all these to functional tests. ๐Ÿ™ See https://git.drupalcode.org/project/experience_builder/-/merge_requests/1....

    I'd just like to see https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... removed or clarified now, assuming @tedbow is +1 to the commits I pushed in #25.

  • Pipeline finished with Failed
    4 days ago
    Total: 719s
    #544338
  • Pipeline finished with Failed
    4 days ago
    Total: 708s
    #544371
  • Pipeline finished with Success
    4 days ago
    Total: 1031s
    #544398
  • Pipeline finished with Success
    4 days ago
    Total: 900s
    #544427
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    I merged in 0.x. there were a lot of JS changes which I think I got right, but will shall see ๐Ÿคž๐Ÿป

  • Pipeline finished with Failed
    4 days ago
    Total: 488s
    #544440
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    Went through the manual testing again. everything ok

  • Pipeline finished with Success
    4 days ago
    Total: 1680s
    #544442
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    Re #26

    1. I have create the follow-up ๐Ÿ“Œ Convert Autosave conflict tests using AutoSaveConflictTestTrait into functional test to test caching Active and added todos https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...
    2. reviewed changes made that were referenced in #25

      Mostly it look good, updated some comments https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...

    3. Only problem with the expanded test coverage @wim leers added was that auto-save entry does not get deleted for non-config entities when the entity is saved, because that happens via \Drupal\experience_builder\AutoSave\AutoSaveManager::onXbConfigEntitySave(). fixed in https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...
  • Pipeline finished with Success
    4 days ago
    Total: 721s
    #544471
  • Pipeline finished with Running
    4 days ago
    #544481
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    #30:

    1. ๐Ÿ‘ Thanks!
    2. Yay, thanks for the rectification, hope this will help the next reviewer/maintainer ๐Ÿ˜Š
    3. ๐Ÿค” Isn't that a bug then? How does an auto-save entry targeting a no-longer-existing-as-it-was content entity (revision) make sense?

    Going through this one last time ๐Ÿค“๐Ÿคž

  • Pipeline finished with Failed
    3 days ago
    Total: 19465s
    #544708
  • Pipeline finished with Canceled
    3 days ago
    Total: 233s
    #545005
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA

    #31 3)
    I think maybe we should be calculating autoSaveStartingPoint in \Drupal\experience_builder\AutoSave\AutoSaveManager::saveEntity() in but only in the case where $original_hash === NULL, where know there wasn't an existing auto-save entry so we actually do know the starting point.

    Calculating in \Drupal\experience_builder\Controller\AutoSaveValidateTrait::getClientAutoSaveData might actually be problematic.

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    This looks solid now, thanks @tedbow! ๐Ÿฅณ

    Two follow-ups needed:

    1. #30.3 is AFAICT a bug that needs follow-up โ€” it means garbage can pile up in auto-save? I just tested it and it allows me to continue editing, but then I end up being stuck with this validation error:

      The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.

      So: it's nice that this keeps your existing auto-save, but then you kinda can't do anything with it? ๐Ÿ˜… I think we need a follow-up to make this more granular for content entities: it should be possible to discard all changes , which would then allow you to "re-target" your existing work to the latest content entity revision.

      This is of course a pre-existing problem, so not blocking this MR over it. It's just that the new test coverage here and https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... in particular made much more obvious.

    2. I suspect a bunch of complexity in this issue could have been avoided if we'd updated \Drupal\experience_builder\AutoSaveEntity instead of sprinkling extra cacheability logic all over. But this too is not being introduced here. Described in some more detail what I think would significantly simplify all this: ๐Ÿ“Œ Add methods to `AutoSaveEntity` value object to simplify auto-save infrastructure Active .

    IOW: I created 50% of the needed follow-ups already :D

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States tedbow Ithaca, NY, USA
  • Pipeline finished with Failed
    3 days ago
    #545057
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Awesome!

    Can you still create a follow-up for #33.1 though? ๐Ÿ™

Production build 0.71.5 2024