Problem/Motivation
If I run the multilingual moderation test and look at the database after, I get something like the following:
mysql> select langcode,revision_id,workflow,moderation_state from test89968991content_moderation_state_field_revision ORDER BY langcode;
+----------+-------------+-----------+------------------+
| langcode | revision_id | workflow | moderation_state |
+----------+-------------+-----------+------------------+
| en | 1 | editorial | draft |
| en | 2 | editorial | draft |
| en | 3 | editorial | draft |
| en | 4 | editorial | published |
| en | 5 | editorial | published |
| en | 6 | editorial | published |
| en | 7 | editorial | draft |
| en | 8 | editorial | draft |
| fr | 1 | NULL | draft |
| fr | 2 | NULL | draft |
| fr | 3 | NULL | published |
| fr | 4 | NULL | published |
| fr | 5 | NULL | draft |
| fr | 6 | NULL | published |
| fr | 7 | NULL | published |
| fr | 8 | NULL | draft |
+----------+-------------+-----------+------------------+
'workflow' is defined as a translatable field, so when the translation is added, the workflow isn't copied from the source language.
Proposed resolution
Either:
- Make workflow non translatable (why is it in the first place?)
- Copy the workflow field from the source translation to the new, when creating the entity.
Remaining tasks
Agree, patch.
User interface changes
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #43 | 2881645-43--test-only.patch | 3.15 KB | sam152 |
| #43 | 2881645-43.patch | 3.85 KB | sam152 |
| #27 | 2881645-27.patch | 4.63 KB | tstoeckler |
| #27 | 2881645-27--tests-only.patch | 3.67 KB | tstoeckler |
Comments
Comment #2
sam152 commentedSame query with the given patch applied:
Comment #3
timmillwoodI guess we need a test for this?
Even though we are still an alpha experimental module we don't need an upgrade path (although I'd love one).
Comment #4
sam152 commentedComment #5
sam152 commentedWhat are we testing here, that the definition of that field on the entity is not translatable? It's one of those ones that I would argue doesn't really need a test, based on it not being a bug. The field was behaving exactly to definition.
Added the tag for the 8.3.x => 8.4.x upgrade path.
Comment #6
timmillwoodwe're testing that when adding a translation the workflow column is set.
Comment #7
sam152 commentedI was assuming that was already a tested behavior of entity API + translation system. Can fairly easily add a test though.
Comment #8
timmillwoodI was just thinking that previously it was being set to NULL and we didn't know because there was no test coverage.
Comment #9
jhedstromWell, #2873287: Dispatch events for changing content moderation states provides an implicit test to prevent this from regressing in the future... I'd agree it seems a bit odd to re-test entity api and translation here...
Comment #10
timmillwoodok, accepting the arguments in #7 and #9 lets RTBC.
Comment #11
tstoecklerI don't see any reasoning above on making the workflow not-translatable. While it may not be the 80% use-case, it seems totally valid to have different workflows for different translations of content. Especially in a context where you have an actual editorial + translation workflow. You might have an "editorial" workflow for the source content, but then have a "translation" workflow for each of the translations.
Maybe supporting that would be overly complex, but would like that verified first.
Comment #12
jhedstromI think having a different workflow for languages for a given entity/bundle would be a new feature, since that isn't currently possible. I enabled the config translation module and attempted to translate a workflow, but all that can be changed is the labels, none of the entity/bundle to which it applies was available to translate.
Given that, we probably do want a test here so that it's explicit that the aforementioned feature doesn't yet exist. I'll see if I can pull a test together.
Comment #13
jhedstromWithout querying the sql table directly during the test, I wasn't able to find a way to readily test this in the tests themselves. However, adding an assertion to the entity operations class where these gets loaded nicely demonstrates the current bug, and also indicates that different workflows per-language is currently not supported.
Comment #15
sam152 commentedI think @tstoeckler is getting at the fact that you might want to apply a different set of moderation states and transitions to different languages of content, not translation of the workflow config entity. Ie, if I'm creating a German translation, I might configure content moderation to skip the "review", given my German copywriter's uncanny attention to detail ;)
This feels like a bit of a product management decision. My perception is translations and forward revisions already have enough of a complex mental model, but that's not to say others might have the use-case.
Comment #16
timmillwoodI agree with #11 that a different workflow per language could definitely be a use case. However I also agree that this may be overly complex, and for that reason maybe something we don't want to support. Supporting that also makes this issue a little more complex to fix.
Awesome to see that @jhedstrom was able to find a way to test this!
I still stand by my RTBC and support the patch in #13. However leaving this as Needs Review for more discussion about different workflows per language.
Comment #17
tstoeckler#13 is definitely awesome, I will try to find some time to make that pass, while still keeping the field translatable. Depending on how much effort that is, I think we can then decide whether it makes sense to support it or not. Is that fair?
Comment #18
jhedstromHaving considered this for a few days, I don't think the assertion I've added is the right way to go if we want these to be configurable per-language. With this assertion in place, even if core doesn't provide that UI, contrib could, and this would prevent that from working.
Instead, we should probably just query the database records directly in the
DefaultRevisionStateTest::testMultilingual()to demonstrate this current issue, and whatever fix is put in place.Comment #19
jhedstromHere's a patch with the alternate test.
I think the way to fix this while keeping the workflow flexible per-language would be to set the workflow value to the default language's workflow if it isn't already set. I'm not sure what the best practice on that is for translations, but this can't be the first place in core that's needed... I spent a bit of time trying
preSave, as the node module uses that to set revision owners, but that didn't seem to work.Comment #21
sam152 commentedI was thinking of simply asserting the count of rows with NULL is 0 at the end of the test. It seems like it's retesting an abstraction we can already rely on to me, but I think it's okay generally.
@tstoeckler any update on #17, otherwise I think this is RTBC.
Comment #22
tstoecklerThis should be enough to support per-translation workflows (at least on a storage level). So from my perspective the effort is reasonable to further support this use-case. The interdiff is relative to the TEST-ONLY patch in #19 and the patch contains those same tests and will (hopefully) be green.
Comment #23
tstoecklerI think this should use an entity query instead.
Something like (untested):
Comment #24
sam152 commentedThis makes a lot of sense to me, if we want to keep the field translatable. I think you've made a good enough case, and this fix is simple enough, that we can accept this even if there is no immediate plans for different workflows per-language in core right now.
As far as the tests go, I seem to remember not being able to get it to work with EQ, but not too fussed with where we end up on the test front.
Comment #25
timmillwoodThis looks like a good solution.
Although I fear if a translation is added without going through EntityOperations::updateOrCreateFromEntity() then we'd still have the problem. One solution there would be to extend \Drupal\Core\Entity\ContentEntityBase::addTranslation within ContentModerationState?
Comment #26
jhedstromI was unable to get entity query to work when I tried previously. However, we could change the test just to assert that there are no rows with a null langocde at the end as @Sam152 suggested above in #21.
Comment #27
tstoecklerOK, switched the test to an entity query. The dummy code in #23 didn't quite work, but almost. Also took the liberty of updating the docs to make them (to my mind) a bit clearer.
Not really sure, what to respond to #25. It seems if someone is manually creating translations of content moderation state entities they know what they're doing and can provide the workflow value themselves? That would be my gut reaction, but - like I said - not really sure...
Comment #29
sam152 commentedPersonally I think the situation in #25 is fine, if you're manually manipulating the entity, you're on your own in a way, plus you might genuinely want to change the workflow for that translation as described in #11.
+1 RTBC.
Comment #30
timmillwoodok, let's do this then!
Comment #31
catch#13 looks much more straightforward to me.
If contrib wanted to support workflow-per-translation could they not alter the field definition to do that?
Comment #32
sam152 commentedIf a field already has data is it possible to change it from non-translatable to translatable?
Comment #33
tstoecklerNo, if a field already has data that's not possible, at least not as far as I know.
Re #31: My reasoning is the following: Having per-entity workflows is already something that we don't support through the UI, but it's something that the data model we* have chosen for Content Moderation (i.e. the dedicated content moderation state entities) more or less required us to do, or at the very least it made the code much nicer to read, to fetch the workflow directly from the state, instead of going through hoops to fetch the bundle, etc. So depending on one's viewpoint it's already either a denormalization of the per-bundle workflow information or a "hidden feature" that would make it significantly easier for a contrib module to implement per-entity workflows than it otherwise would.
Now if you argue for the former I can see how fixing this bug with the minimal possible amount of complexity/code is desirable and that would probably be #13. But I would strongly argue in favor of the latter point: That without actually going through hoops we are enabling contrib do to fancier stuff. And per that viewpoint we are currently (i.e. in HEAD) enabling contrib to do - not only per-entity - but per-entity-translation workflows. And #27 fixes this bug, without removing that capability.
Additionally, I think #27 still couldn't be considered as "jumping through hoops" to enable a contrib use-case. I can see how it's slightly less nice from a pure code complexity standpoint than #13, but I don't think we are increasing complexity or maintenance cost or anything here, at least not in any significant manner.
(Note: I'm using "we" as "all of us/the community" here, I personally didn't actually write any of the Content Moderation code, nor was I significantly involved otherwise. Not sure if that makes sense, not a native speaker here, sorry...)
Comment #34
timmillwood#13 leads to a harder upgrade path, and becuase it's experimental there will be no upgrade path, adding yet another thing for some people (@Sam152) to deal with. For that reason alone #27 looks like a nice solution. It's not like #27 adds loads of new API layers either, it's quite a neat solution. #27 is not as fool proof as #13, but maybe we need to worry about the fools when they come along.
Comment #35
plachActually, it is totally possible and it does not even require an update. Just switch the value and save the definition and you're done.
It would be nice if core had no bugs preventing people from having different workflows per language, but I'm not sure this is the kind of use case we need to support in core.
Comment #36
sam152 commentedI tested this, there was indeed no pending entity updates required after the value was changed. If only it was this easy for non-revisionable/revisionable fields :)
Comment #37
plachYep, obviously when you do the switch only the original language has values, but you can easily implement a custom logic to fall back to the "default language" workflow if none is available for the current language.
Comment #38
tstoecklerOh, sorry for my misinformation in that case, I wasn't aware of that. And thanks @plach, for setting me straight!!
Comment #39
plachGlad to still be useful from time to time :)
Comment #40
timmillwood@tstoeckler would you support the patch in #13? (or even #19 with the updated test).
Comment #41
tstoecklerWell, I don't really want to hold it up, especially since it's not something that I won't be able to workaround in contrib/custom code. It just seems to me very unfortunate to require people that want per-translation workflows to have to employ some workaround - be that in a custom module or installing a dedicated contrib module for that - while people that want per-entity workflows can just go ahead and build a UI on the core API even though neither are supported by the core UIs. Unfortunate in particular because it requires so little code to support/fix.
Comment #42
amateescu commentedFWIW, I would favor #27 over #13 mostly for the points @tstoeckler mentions in #41.
Comment #43
sam152 commentedI think #13 is better. Until the decision is made to actually support assigning different workflows to different languages in core, why not go with the fix that removes some bit of code instead of adding some?
Also, if I'm trying to build this feature in contrib, to undo this behavior, I can simpy do a
hook_entity_base_field_info_alter, switch it back to translatable and be done. How do I go about making sure #27 doesn't kick in?I think we nailed the test in #27, so I propose the attached.
Comment #45
sam152 commentedComment #46
timmillwoodTo try and get this issue back on track and stop the bike shedding lets look at the pros and cons of the two options:
Known as #13.
Advantages:
- Simple change.
- Effective API wide.
- Easy to update / change in contrib.
Disadvantages:
- Needs an update / change (workaround) in contrib (or custom) to allow per translation workflows.
Known as #27.
Advantages:
- No API change.
- Contrib or custom modules just need to implement a new UI to have per translation workflows. (as @tstoeckler mentions in #41 but as @Sam152 says in #43 how do we go about that? it seems more complex than it sounds)
Disadvantages:
- Only kicks in if going via EntityOperations::updateOrCreateFromEntity().
After reviewing the options I am in favour of what we've been referring to as #13, but the latest patch in #43 has better testing for this. It seems much simpler for contrib/custom modules to override, which is what @tstoeckler is arguing for. Also I believe we could always make it translatable in the future in a BC way, where as making it non-translatable in the future would be a BC break.
Leaving as Needs Review for now, but will RTBC #43 in a few days if there are no objections.
Also, please correct me if any of my pros and cons are incorrect.
Comment #47
timmillwoodAs mentioned in #46, I'm RTBCing the patch in #43 because it fixes the issue and is easy for contrib to override.
Comment #48
tstoecklerSorry, but I feel like I need to rectify some things. Since I'm only slightly annoyed by this issue and it's not a huge deal, I would have let this go, but since #46 explicitly mentions me I think I need to clarify my position.
The parenthesis is pretty unfair and is a mis-statement of my earlier point. My point was that - in the data model - we already have per-entity workflows, even though that is not visible in the UI. I.e. we already have a separation of the UI concepts - which strictly enforce to have the same workflow for all entities of a bundle - and the data storage. My point is that since we already have that separation anyway, for reasons completely irrelevant to this issue, it is very reasonable to take this separation just an inch further to have per-translation workflows instead of per-entity workflows.
Providing a UI for either per-entity or per-translation workflows is certainly a daunting task, but the outcome of this issue doesn't change that, so the way the quote is states is misleading, in my opinion.
While this is true, technically speaking, I have yet to hear any actual reason this is a problem. In fact we are actively removing any direct access points to the content moderation state entities currently in core (#2843753: Prevent ContentModerationState from being exposed by REST's EntityResource, #2894479: Deprecate the Views relationship from moderated content to the "content_moderation_state" entity ), so bringing up the possibility of manually creating content moderation state translations strikes as a bit inconsistent at least. In fact, if one were to experiment with a per-translation workflows UI in contrib, one would do exactly that, and then one would expect to have to explicitly specify the given workflow. So I don't really see this as a striking argument.
To me the only real argument is the added maintenance burden of the code added here, but - as also stated above - as its really very few lines of code and really just an extension of the pre-existing complexity that is inherent with such an internal/hidden entity type, I think the benefits outweigh this downside.
Sorry, but can you explain that? The way I read it this is a pretty clear untruth, but I'll give you the benefit of the doubt that I'm simply not getting what you mean by that.
Comment #49
sam152 commentedRe: #48, what I meant was, if you are a contrib module trying to implement the feature you are describing, if you wanted to return to the scenario outlined in the issue summary, where the 'workflow' column is empty for new translations, you can simply implement hook_entity_base_field_info_alter and change the field from non-translatable back to translatable. @plach pointed out there are no entity table layout changes and all you'd need is a cache clear after changing this.
With the alternative implementation, I don't see as clearly how you'd hook into this and prevent the workflow field from being set in the new translation in the first place.
Have I missed the mark in my understand of this?
Comment #50
timmillwoodRight, that's my understanding @Sam152.
My statement "It seems much simpler for contrib/custom modules to override, which is what @tstoeckler is arguing for." was trying to summarise that we're looking to allow contirb to have per translation workflows. It seems like the easiest way to do this, is implement hook_entity_base_field_info_alter and make the field translatable.
I don't mean to annoy, upset, or be unfair, and to be honest I don't really care too much I just want this issue fixed.
Comment #51
timmillwoodThis issue is currently blocking #2873287: Dispatch events for changing content moderation states.
Comment #52
tstoecklerAhh, I think I understand now what you are getting at.
So basically you are saying it is easier to make the field translatable from contrib (if it is untranslatable in core) than to make it untranslatable (if it is translatable in core)?
I'm not sure, maybe @plach has more info on that, but I don't necessarily think that's the case. Since there's no table layout change either way, it seems it would work either way and would be equally simple either way?
Comment #53
sam152 commentedI think the point was, if I'm a contrib module that has decided to fully assume responsibility for the 'workflow' column, how do I stop this code from setting it for me? I think it would be possible, but this approach was RTBC then knocked back, primarily because the alternative was so easy to undo.
Comment #54
tstoecklerSo I think that really depends on what your trying to do. Since the workflow that is being set here, is determined at the top of the function byyou can have a workflow plugin that determines the workflow based on the language or its translation status, etc.Edit: Sorry, my brain mixed up some similar looking lines, sorry, proper response coming up...
Comment #55
tstoecklerRight, so yeah, I do think it really depends on what you're trying to achieve.
Generally, since this code is invoked from
hook_entity_insert()andhook_entity_update()you can implement the same hooks and - depending on whether you run before or after (which you can control reliably (albeit not elegantly) by setting the module weight) - either create a content moderation state translation before this code runs - in this case the (pre-existing)->hasTranslation()will respect your choice and not do any manual processing - or after in which case you can simply override the workflow to your desired value and re-save the content moderation state translation.If what you are actually trying to do is have some deterministic logic behind which entity receives which workflow, you probably want to swap out the
ModerationInformationclass and in particular itsgetWorkflowForEntity()method. That is what you would be doing anyway in this case, and as the code snippet in #54 shows, the added code will respect this, as well.Comment #56
sam152 commentedGreat analysis, it seems there is a general approach for making this work with what you proposed in #27, so I take it back that the patch would somehow preclude us from supporting per-translation workflows.
Despite all that, I think #31 and #35 still stand and altering the definition is easy enough to be considered an acceptable fix.
If you're happy with that @tstoeckler, I think RTBC is the correct status.
Comment #57
tstoecklerOK, so we have that cleared up at least, that's good. Thanks @timmillwood and @Sam152 for clarifying your positions.
I wouldn't go so far as to call myself "happy", but I don't want to be in the way of this going in.
Comment #59
timmillwoodMoving this back to 8.4.x, this is a blocker for a "should have" issue which is needed for Content Moderation to become stable in 8.4.x
Comment #62
catchCommitted/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!
If we run up against issues supporting translation issues, then let's absolutely open those, but happy to see the simpler fix RTBC.