Problem/Motivation
Currently the node UI prevents concurrent edits of the same node. This however does not take into account the case of multiple translators editing different translations of the same node.
Additionally this logic is applied only to nodes, but it would make more sense to generalize it to any entity type relying on the entity form handler.
Proposed resolution
- Implement per-language locking, thus preventing only concurrent edits of the same translations.
- Extend this check to any entity type.
Remaining tasks
- Validate the proposed solution
- Implement it
- Review patches
User interface changes
Concurrent edit locking would be enabled on any entity edit form.
API changes
None foreseen
Comment | File | Size | Author |
---|---|---|---|
#36 | test_translation_data_loss.zip | 829 bytes | RoSk0 |
Comments
Comment #1
plachThis should be easier to implement if we do #2465913: Write to the storage only affected entity field values first.
Comment #2
plachThis might be actually postponed on #2297817: Do not attempt field storage write when field content did not change, #2465913: Write to the storage only affected entity field values was marked as a duplicate.
Comment #3
plachThe blocker has been committed.
Comment #4
plachComment #5
xjmSee https://groups.drupal.org/node/484788 for more information on the rc target tag. I'd need some more detail on the impact vs. potential disruption of this change to assess whether it could be committed during RC or not, myself. :)
Comment #6
xjmUntagging for now.
Comment #7
mkalkbrennerThis feature makes sense!
But the implementation details might become sophisticated because an entity might consist of translatable and untranslatable fields.
Before we start implementing something, we need to define exactly how per-language locking should work in the context of untranslated fields.
While pessimistic locking should lock all translations if the entity contains untranslatable fields, optimistic locking has to check for changes on untranslatable fields on submit. If the untranslated fields did not change, we need to merge in between changes to these fields from other translations before we save.
Comment #8
plachI agree.
I think ideally we could even generalize this "merge approach" and allow any concurrent changes that did not affect the same field values. This would automatically allow for concurrent translation edit, since translatable field values would never generate conflicts when different translations are involved. However it would also support concurrent edits of the same translations as long as they do not touch the same fields, otherwise we would get the current behavior.
This would probably be a powerful feature for editors (and probably close to a VCS merge experience :) but I guess we should aim for the multilingual stuff for now and maybe "relax" these constraints in a later iteration.
Thoughts?
Comment #9
plachI guess this is either 8.0.0-RC1 or 8.1.0 material.
Comment #10
plachAnother tag
Comment #11
webchickHey there!
We're closing up shop on RC1 here very, very shortly, so I think the most realistic target for this is 8.1. Updating version/status to reflect that.
However!
+100 for the general idea of this feature. I think it would be awesome not only for translators, but also for any sites with multiple content authors. The proposed implementation sounds very similar to the https://www.drupal.org/project/nodechanges module, which is used by Drupal.org for issue comments that change statuses (this is actually changing node revisions behind the scenes). So that'd be one fewer module for D.o to port as well. :)
Comment #12
mkalkbrennerImplementing optimistic locking by auto-merging all non-conflicting fields is a very technical approach. For Developers that are used to tools like git this sounds great, but if you think twice you'll notice, that this is definitely not a "one size fits all" solution!
Examples:
You have an untranslated image field in combination with a translatable text field for it's caption. It makes no sense to auto-merge a concurrent edit to the caption in one translation when the image has been changed in between.
Or think about commerce: One decides to "promote" an item by selecting the corresponding checkbox while another one marks it as "out of stock" in between.
These kind of merges are even more dangerous if you don't create a new revision!
I think we need to decide very careful here, like the decision for #2465907: Node revision UI reverts multiple languages when only one language should be reverted.
I would like to see a generic configuration option in core to switch between optimistic and pessimistic locking. In case of a merge we need to support the decision of what to merge.
I recommend that we let this sophisticated stuff incubate in contrib.
In Revision UI we're currently working on a form that lets you select single fields to be reverted. The corresponding list of fields is limited to fields that really changed between two revisions. Maybe this approach - or even better the code - could be re-used here for merging field changes.
BTW By using parts of HTML Diff it will be possible to really visualize the difference between two field values, like tracking changes in Word.
Comment #13
plach@mkalkbrenner:
This is 8.1 (at least) now, so we have all the time to find a proper solution in contrib and evaluate core inclusion later. Thanks for starting looking into this!
Comment #15
xjmI think this can be active now, since feature development is open for 8.2.x.
Comment #17
matsbla CreditAttribution: matsbla commentedI tested now to edit different translations of a node concurrently, and it seems like working fine!
Have this issue been fixed?
Comment #18
BerdirNow that we per-language changed tracking, this might look like it works, yes.
But I'm worried it will actually result in data loss. Can you check that? Is the data for the other translation list if you do something like this (using EN and DE, you can use any language combination):
1. Open first node form in language EN, doe an ajax request like add another item to make sure form state is cached
2. Open second node form in DE, make a change, save.
3. Save the first form with language EN, making another change.
Is your DE change still there?
Comment #19
mkalkbrennerI don't think that this issue is solved. (At least the use-cases in #12 are not addressed.)
But the current behavior you see might change again once there's progress on #2808335: Changed time not updated when only non-translatable fields are changed on a translated entity and #2615016: ContentEntityBase::hasTranslationChanges should exclude the "changed" fields from the comparison.
Comment #20
hchonovHm how would this be fixed? I can imagine that it only works if you do not have the EntityChangedConstraintValidator. But actually as soon as we have conflict management this issue would be fixed by it.
Comment #21
matsbla CreditAttribution: matsbla commentedOkay I tested with an image field that is untranslatable (but with translatable alt and title value).
I open form for 3 different translations. I add a picture to one of them with translations and saved it. After I saved the other translations, but the image is still only visible for the translation where I've added it, and I can now even add other images to the other translations (even though the image field itself it set to untranslatable).
Comment #22
hchonovI am confused as in this case the EntityChangedConstraint should have failed and you shouldn't be allowed to save the other translations, but it looks like the constraint is not failing during your manual test. Could you please try to reproduce it in a test?
Comment #23
matsbla CreditAttribution: matsbla commentedI'm not able to make any test, but I can see if I manage to get help with it.
I tested more, it seems like what I experienced is only present with the default content type Article using the default image field that is shipped with core, I made one issue here #2837023: Image fields that follow core gets broken when set to translatable
However it seems like the concurrent editing of translatable image fields (with untranslatable files) is still not working. Currently EntityChangedConstraint seems to not fail, so old image is saved over new image when someone edit another translation while you try to add a new image, I made an issue for it here: #2837022: Concurrently editing two translations of a node may result in data loss for non-translatable fields
Comment #24
matsbla CreditAttribution: matsbla commentedOne question; is EntityChangedConstraint only suppose to conatrain cuncurrent editing of same translation? Or also cuncurrent editing of untranslatable fields? It seems like untrabslatable fields are not constrained now so if someone edit them cuncurrently they will revert any changes made on these fields.
Comment #26
vlad.dancerComment was moved to the https://www.drupal.org/node/2837022#comment-11930862
Comment #27
vlad.dancerComment #28
vlad.dancerMoved my patch to the better place, see #2837022: Concurrently editing two translations of a node may result in data loss for non-translatable fields
Comment #30
hchonovWe've just implemented this in #2911532: Lock on entity translation level instead on entity level in the content_lock module.
Comment #35
RoSk0I believe we need to extend the scope of this issue to add not only per-language locking, but also per object-locking for translations. On one of the client projects we faced with the issue of loosing published translation when multiple editors publishing different translations of the same node at the same time.
Steps to reproduce:
sleep(1);
. This small hack will help us in replicating long processes that run on big complex sites. You can find it attached. Install and enable it.I also recorded screen capture of the process on clean Drupal installation following steps above. Link: https://youtu.be/Un8rc8BnJhk
Comment #36
RoSk0Forgotten module.
Comment #37
hchonov@RoSk0, this a content_moderation issue and you should report it in the issue queue of the module. Default core behavior wouldn't have allowed you to save multiple tabs of the same entity without having the conflict 2.x module enabled. This means that content_moderation somehow interferes with the core protection mechanisms or overwrite them and introduces its own.
Comment #41
matsbla CreditAttribution: matsbla commented