Problem/Motivation
We have defined a workflow with these states:
- Draft
- Needs Review
- Published
We have two roles:
- author (Who creates content in draft and sets it to "needs review")
- admin (Who can transition from "needs review" to "published")
Steps to reproduce
- Enable the Content moderation and Content translations modules, and their requirements.
- Add another language to the site.
- Enable translation for Content (nodes) for at least one content type.
- Enable the Editorial workflow for at least one translatable content type.
- Configure the workflow with the 3 states listed above (draft, needs review, published), and 4 transitions:
- draft -> draft
- draft -> needs review
- needs review -> draft
- needs review -> published
- Configure 2 roles:
- author (with permission to use draft -> draft and draft -> needs review transitions)
- editor (with permission to use all of the above workflow transitions)
- An author has created a node in English, and sets it to "needs review".
- Now the author wants to add a translation for this node. They go to the Translate tab and click on the "Add" Button.
- The form where they can edit the node and add the german texts is displayed.
- When they click "Save", they gets the error message "Invalid state transition from Needs review to Needs review".
Logically that is correct, because the author role doesn't have the permission to edit nodes which are in the state "needs review".
But shouldn't new translations be treated as new nodes and therefore be created as "draft"?
Proposed resolution
Preserve the existing behavior for BC, but add a way to configure this when defining a workflow.
Whatever new UI is introduced should only be visible if both Content moderation and Content translation modules are enabled.
Option B
(Other rejected proposals removed, this is what we're going with).
- Have a simple toggle for the current behavior vs using the default moderation state (same as A)
- UI is 2 radios.
- Setting stores a string, ID would be 'translation_default_moderation_state_behavior' or so, with values like 'current' vs. 'default'.
- In some UIs, explicit radios for different options can be more clear than a checkbox (if each choice requires a little explanation).
Example UI:
Initial moderation state when adding a new translation:
(*) Default moderation state
( ) Current moderation state of the original content
Open questions
Which UI is best for the setting?Option B - radio buttons. See #45Since all the code handles no value for this setting as if you want the current behavior, do we need an upgrade path and all that complication?Yes (@larowlan in #42)Do we want to add this setting to the default workflows shipped with core?YEScore/profiles/standard/config/optional/workflows.workflow.editorial.yml? YESIf so, should we set it to 'default' or 'current'?Using 'current' per @larowlan in #42Also demo_umami?YES
Does this issue need a CR?YES: https://www.drupal.org/node/3240957Do we need more test coverage?YES:Should the #default_value for these radios use 'default' for new workflows? It's now defaulting to 'current' if missing from the config for BC on existing sites. But presumably we could notice when creating a new workflow and use a different #default_value to be the more intuitive 'default'.NO, leave it as 'current' (per @larowlan in #42).Where is the right layer of the entity / translation / content_moderation systems to specify the default moderation state of a new translation? See comment #33. We don't want to have to rely on field widgets to enforce this setting. It'd be better if we directly changed the moderation_state when populating a new translation so that field widgets don't need to support this setting, and so it works if there is no widget for moderation_state. Should we?- Use
hook_entity_translation_create()YES. Directly hackNOEntityOperations::updateOrCreateFromEntity()? This seems problematic since we'll have a hard time knowing if the entity has a value from a widget or if its own override logic should kick-in.Other?
- Use
What should the UI help text say for the "Legacy" config option?See #101.
Remaining tasks
Answer the open questions.Implement the new setting / UI.Don't check if the user has access to this transition (because it should be treated as new).Add a DB update for existing sitesAdd tests:What you see when creating a new translation (the bug)The two different roles case described in the summary.Done in #47The upgrade pathThe UI for this when creating a workflow.Done in #47.
Decide what to do that the legacy behavior is not "Current moderation state of the original content" as the UI suggests, but actually a hardcoded choice between exactly 'published' (if the original content is currently in any published state) or 'draft' (for any unpublished current state). See #91UX team proposed a solution in #101, implemented in #104- RTBC
User interface changes
A new setting when creating/editing a workflow:
Before
What you see without the patch applied, or when content_moderation is enabled but content_translation is not, at paths like /admin/config/workflow/workflows/manage/editorial:

After
With the patch applied, and both content_moderation and content_translation enabled:
Initial moderation state when translating existing content*
(*) Default moderation state
( ) Published if existing content is published, otherwise Draft

API changes
None.
Data model changes
New config schema for a setting on workflow type plugins.
Release notes snippet
TBD.
| Comment | File | Size | Author |
|---|---|---|---|
| #137 | 3150294-11.3.x-137.patch | 14.31 KB | frevda |
| #136 | 3150294-11x-136.patch | 14.39 KB | smulvih2 |
| #135 | 3150294-11x-135.patch | 14.27 KB | joseph.olstad |
| #130 | 3150294-128.patch | 14.51 KB | mahde |
| #127 | 11x-3150294-127.patch | 137.25 KB | joseph.olstad |
Issue fork drupal-3150294
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
bahuma20Comment #3
bahuma20Comment #4
bahuma20Comment #5
bahuma20After some digging in the code, i've found out that there is a problem with the function ModerationInformation::getOriginalState().
This function should return the initial state of the workflow for new translations.
I've added a patch that adds this functionality.
Comment #6
bahuma20Comment #7
bahuma20Comment #8
sam152 commentedThis has come up a lot in the queue and was raised by @timmillwood before content moderation hit a beta status.
I feel confident that changing this now would break workflows setup for existing users. To that end, this needs to be marked a feature request and a fix would need to allow for existing users to continue using the system the way it currently works. An alternative may also be to document a code-based way of overriding this, instead of building it into core.
Comment #9
sam152 commentedComment #10
sam152 commentedComment #11
sam152 commentedNW to discuss #8 and for tests.
Comment #12
sam152 commentedComment #13
bklineInteresting pattern (twice in one week).
Does @Dries still wonder why it's hard to find enough contributors to open source projects?
Comment #14
sam152 commentedHi @bkline,
This issue examining the starting state of new translations predates the issue you opened as well: #2927455: When adding a new translation the entity should start from the draft state. You could argue all the follow-ups should be closed in favour of that one. Personally I don't mind too much, since this one brings an up-to-date title, summary and steps to reproduce to the table (the customisable initial workflow state wasn't a thing back then), but if anyone feels strongly about that I'd be fine reorganising it.
Re: feature request, my reading of these issues so far is that fixing the problem for some users may negatively impact other users. Anyone can change the category back if they disagree.
Not really interested in responding to your other points, it'd be wise to remember a lot of contributors are volunteers.
Comment #15
bahuma20A suggestion:
We create an update hook that sets a flag in Drupal::state which indicates that new translations should be created the same state as original translation currently is.
The update hook would never be invoked on sites where the modules wasn't installed in the past.
So only new sites would create the translations in the default workflow state (the expected, correct behavior).
But i don't know if Drupal::state is the right place for this, because it is difficult to change, if users would like to enable the fixed behavior.
Comment #16
bahuma20Just a thing to note...
When the new translation is created in "draft" state, the access checks for transitions are invoked, and users that don't have permission to transition from "needs review" to "draft" cannot create the translation.
I couldn't find the code that causes this, but i think we have to fix this in this issue, too...
Comment #17
bpizzillo commentedHi @bahuma20,
There are two additional changes that I think need to be made, one easy to explain, one much harder.
Change 1 - Fix the ModerationStateWidget to use the state of the translation
Issue #2914873 added in an additional hoop to this problem. When a new translation is “created,” and the edit form is displayed, the widget gets the workflow state of the as of yet translated item. It calls `$original_entity = $original_entity->getTranslation($entity->language()->getId());` which actually will return the original entity that was being translated. So the same check for isNewTranslation you added to `getOriginalState` needs to be added to this as well. Looking something like:
Change 2 - Fix how moderation_state get set in the first place for a new translation
So one of the major issues with how everything works is that the `moderation_state` field is a calculated field, so it show up just like any normal field. The when a new translation is created, the translation system makes a copy of all the fields of the entity -- including `moderation_state`. This was purposefully done in #2915398 to copy the moderation state from the original item instead of using the workflow default for the translation. So if your original language content has been published, when the translation is created the `moderation_state` “field” is copied into the entity.
So what can be done is to implement a hook for `hook_entity_translation_create()` and change the moderation_state to whatever the workflow default is. You cannot just set the field to NULL and have `getOriginalState` work things out because the `moderation_state` field MUST be set to a valid state. (It probably also makes sense in a way since you would never really want to make that field NULL when an item has entered into a workflow) (getOriginalState could probably be a static method that can be called by this hook and the new node creation)
This would look something like:
Possible Better Solution for 2 (still requiring change 1)
Probably the best solution would be to add a flag to the workflow to "Use default workflow state for translations" and then have the `moderation_state` computed field class determine if it should get the state from the original item, or use the default workflow state. (Or it does it by implementing a entity_translation_create hook) This would support the current use case where translation workflow follows the original item. Additionally it would support this use case where translations lag behind the original language, as in the case where a site owner has more "original language" writers than "translated language" writers, but still wants to support other minority populations in their community. In those cases the "original language" could be published weeks before the "translation language" versions as workload allows, while still allowing the full workflow for the "translation language" content.
Comment #19
nixou commentedHi,
On our side we need the same feature as describe above : a new translation has to be initialize in draft state.
This works well with the patch 3150294-5.patch.
But it remains a problem : the "destination" states proposed in the "Change to" list are not the one authorized for the draft states.
This "Change to" list behave as if the current state was the one of the original node.
This is solved by the "Change 1" describe by @bpizzillo.
Here is a new patch based on 3150294-5.patch + the "Change 1".
Comment #20
maruthachala moorthy e commentedonly in patch2:
unchanged:
--- a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
+++ b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
@@ -125,7 +125,7 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
// If the entity already exists, grab the most recent revision and load it.
// The moderation state of the saved revision will be used to display the
// current state as well determine the appropriate transitions.
- if (!$entity->isNew()) {
+ if (!$entity->isNew() && !$entity->isNewTranslation()) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $original_entity */
$original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadRevision($entity->getLoadedRevisionId());
if (!$entity->isDefaultTranslation() && $original_entity->hasTranslation($entity->language()->getId())) {
Comment #22
dwwRe: #5: Thanks for finding, reporting and fixing this with a patch!
Re: #8:
- Still needs tests, so back to NW. Also added to remaining tasks.
- I'm in the middle of assessing how translations will fit into a content_moderation-based site and I hit this bug. Yes, I think it's a bug.
- Yes, some sites might already be working around it. I predict most will expect it work like this and will be surprised and disappointed to find that's not the case.
- I think we will have to fix this in a BC way.
- We shouldn't fix this by documenting custom code people have to add to get it to work as most would expect.
Re: #15 and BC ways to fix this:
I think we should add a new property to workflow definitions related to this, give it the correct default, and have the update hook here set the value to the current behavior for existing sites. Everyone wins, and we store the info close to where we need it, not in an entirely separate storage mechanism.
Re: #16 and transition UI:
Good find. I agree, that's probably in scope here. Starting a new translation should really be more like starting a brand new node. We shouldn't consider this a state transition, we should always allow it to anyone that's allowed to create a new translation. Happy to see this is already in the summary.
Re: #17 and fixes:
1. Yes, thanks.
2. Hrm.
2+. Yes! This is basically what I wrote up before I started this careful review of the issue history. ;) See below.
Re: #19: Thanks for fixing #17.1! Please always include an interdiff with a new patch.
Re: #20: Thanks for posting #19's interdiff. ;)
Some ideas on how the new setting should work, which I'm putting in the summary:
Option A
Example UI:
(Don't love having the description, but without it, not sure folks would understand the implications of turning this off).
Option B
Example UI:
Option C
I think I lean towards B, since I imagine it'll be hard to improve A enough to not need a description. If we can come up with a simple, clear name and behavior that requires no description, A would be better. I'm not attached to my initial attempt above. Improvements / alternatives welcome. ;)
Thanks!
-Derek
Comment #23
benjifisherWe discussed this issue at #3223115: Drupal Usability Meeting 2021-07-16. (Sorry for the delay.) There were only three participants, so a lot fewer points of view than we usually get.
We limited our discussion to the UI options suggested in #22 and the issue summary.
It would help evaluate this issue if you describe the necessary steps in the issue summary:
For testing purposes, I think it is possible to have some unrealistic workflow configuration so that even an admin user triggers the error.
Maybe it would be simpler to start with the Umami demo profile. My steps assume the Standard profile.
You did not say in #22 where the new option would be. This context matters! At first, I assumed it would be in the "Language settings" vertical tab of the content type configuration page (such as
/admin/structure/types/manage/article). But it makes more sense to put it on the Workflow configuration page, in the same section as the "Default moderation state" select list.With all that said, I can now answer the question. If it is a boolean option, and if you put it on the Workflow configuration page, then we liked the checkbox option. This seems like the right UI element for an option that modifies how the "Default moderation state" is used. I wish I had a more convincing example, but look at
/admin/config/regional/settings: if you allow users to set their own time zones, then the (boolean) option to nag them is exposed as a checkbox.We did not discuss it at the meeting, but my personal suggestion is to label the checkbox with an imperative statement. For example: "Use the 'Default moderation state' for new translations." or "... when adding a new translation." (I use the same capitalization as in the label: "Default moderation state".)
The imperative and the single quotes are consistent with the Style section of the Interface text page. You should also consider User interface standards.
Comment #24
benjifisherThe issue summary mentions applying a particular patch from #3020387: Moderation state is the same for all node's translations in edit page. That issue has been fixed, with the patch from a later comment. I think the issue summary here should be updated, so I am adding the tag for that.
Comment #25
dww@benjifisher re: #23 Thanks for reviewing it! Sorry this part wasn't clear:
The issue summary already said:
Guess that was too short and was easily missed... 😉
Agreed. Fixed. Also made the scenario gender neutral. Hope it's clear enough now for anyone to understand the problem.
I’d rather it be a checkbox, but I think the wording is going to be very tricky to get right. If it just says:
“Use the 'Default moderation state' for new translations.”
I have no idea how anyone would understand what happens if you don’t check the box. If we go with a checkbox, I think we need a #description, too. In that case, what would it say? That’s why I thought radios might be more clear...
Re: #24: Good point. Applying a patch from #3020387 is now an irrelevant detail. Removed. It's still listed as a related issue, if anyone cares.
Also, added this point to the Proposed resolution:
The setting would only impact anything if both modules are enabled, so we shouldn't display it at all on monolingual sites.
With all of the above edits, I think the summary is fine again, so removing the tag. Anyone is welcome to further improve the summary as needed.
Thanks!
-Derek
p.s. Also crediting @benjifisher for the UI review and feedback, and myself for all the work on the summary, etc.
Comment #26
benjifisherYou can treat the opinion in #23 as a recommendation. The usability team will not block this issue if you choose to use radios.
Is "default revision" used elsewhere in the UI? On
/node/2/translationsI see it described as "English (Original language)".Personally, I think it is clearer to tie the option to the action performed, not the result of that action. Unfortunately, that makes it a little longer:
I have a feeling that there is room for improvement.
Comment #27
dwwHere’s a proof-of-concept patch to get us started on the new setting. Leaving needs work because:
But, this at least gets us a (mostly) working setting on the workflow edit form that is honored by the appropriate code when creating a new translation. Reviews / refinements / improvements most welcome!
Meanwhile, here are some screenshots to show it in action:
/admin/config/workflow/workflows/manage/editorial:
/node/1/translations (when only the original language exists, no translations yet):
/es/node/1/translations/add/en/es (either without the patch, or with the setting configured as "Current moderation state of the original language”):
/es/node/1/translations/add/en/es with the setting configured as "Default moderation state”:
Comment #28
dwwThanks, bot. ;) All those fails are from the same problem:
Easy fixes attached. The existing content_moderation Kernel tests now all pass locally, so I hope the bot is happier this time around. ;)
Also adding test coverage for the setting.
core/modules/content_moderation/tests/src/Kernel/DefaultRevisionStateTest.phpseemed like the easiest/best place to do so, since it could reuse most of what that test was already doing, and this setting is related to the default revision state. If we want to move this into a separate test class, we certainly can.I just added an Open questions section to the summary. Copying here as a snapshot and for folks following along via email. ;)
core/profiles/standard/config/optional/workflows.workflow.editorial.yml?Thanks!
-Derek
Comment #29
dwwComment #30
dwwAdding another open question:
Comment #32
dwwThis should fix the remaining functional tests. 🤞
Comment #33
dwwWell drat, I found an edge case here that doesn't work as expected. 😢
If there's no field widget for
moderation_statevisible on the entity form, when you create a new translation, we still end up with the current moderation state of the original language, not the 'default' moderation state.I'm not yet sure the right layer of the system to fix this, but at least here's a failing test about it. 😉 I also wasn't sure how to prove this with a Kernel test, so for now it's extending
Functional/ModerationLocaleTest.I believe this is due to
Drupal\content_moderation\EntityOperations::updateOrCreateFromEntity()doing this:If there's no widget for moderation state on the translation form,
$entity->moderation_state->valuereturns 'published' here. Is this a reasonable spot to check if it's a new translation and enforce this workflow setting? Or is there a better place to define the default value of a moderation state for new translations if it's not being handled by the form itself?The Kernel test proves that doing all this programmatically via the Entity API works fine, and the setting is respected. But once we're dealing with the forms via the UI, if the widget isn't around to handle this for us, we're still not getting the right initial state.
Can we just directly hack
EntityOperations::updateOrCreateFromEntity()about it?Comment #34
dwwAdded that last point as another open question:
EntityOperations::updateOrCreateFromEntity()about this new setting to handle the case where a translation form doesn't include a widget formoderation_stateto get this right?Comment #35
dwwWhoops, forgot to check phpcs. This is now clear for all of core/modules/content_moderation...
Comment #36
dwwUpdating the open question #7 to be more clear:
hook_entity_translation_create()EntityOperations::updateOrCreateFromEntity()? This seems problematic since we'll have a hard time knowing if the entity has a value from a widget or if its own override logic should kick-in.Comment #37
lokapujyaWhy not give the author role access to edit permission from "needs review"?
Comment #38
dww@lokapujya: For all sorts of reasons:
The most logical thing would be for a new translation to work as if it was a brand new node and start with the default moderation state. That's what this issue allows. However, some sites might be relying on the (IMHO weird) existing behavior, so we're trying to support everyone's possible workflow configurations and needs.
Thanks,
-Derek
Comment #39
dwwI've been trying to get feedback on the open questions, especially #7. However, in the absence of more expert opinions, I'm going with
hook_entity_translation_create()for now since it seems to be the most elegant / simple solution. The function test I added now passes. All the tests pass if we revert the changes tocore/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php, but I think the UI is nicer if we keep those widget changes in the patch. Then, if this setting is enabled, the widget makes things look a little more simple and clear on a new translation. But if we wanted to leave those out, we now can.Since we've got passing tests and a functionally complete patch, back to NR (for now, although it probably needs work, pending the answers to the open questions).
Thanks,
-Derek
Comment #40
joel_osc commented@dww / Derek, thank-you for all of your work on this, I will test your patch shortly. Our use case is that we are using webform content creator to create nodes automatically from a webform, and in code we create the translation and found that it would never take on the default moderation state.
I wish I could help with your open questions but my understanding of this part of Drupal is insufficient. Thank-you again for all your hard work on this really difficult problem.
Comment #41
bpizzillo commented@dww - I have to update the system to D9 where I had to use this approach next week, so I will get this tested at least in our environment. I see a lot has changed since I was last here. Thanks for getting a patch out there, as well as the test setup, which I am sure was the much heavier lift.
I have been thinking that one of the root issues of this problem is how content_translation works. (as you pointed out) Other content management systems I have used usually have 3 options for translating a field.
Option 3 is missing in content_translation and is why there is the hook_entity_tranlation_create "hack". I tried looking a while back if this had been logged as an issue, or if there is a maintainer for content_translation, but I did not come up with anything. If content_translation had an option to do this, then this issue would probably have been less circuitous.
However, at this point, changing translation field options is probably a bit much -- we might as well add the workflow state to the node_field_data table and actually make workflow a first class citizen. (Much like content_translation is)
Comment #42
larowlanI was asked to give my input on the 7 open questions.
Here is my 2c
Comment #43
dwwThanks for answering the questions, @larowlan! Added a CR, updating the summary, tagging for NW, and a few follow-up questions:
Comment #44
larowlanTechnically yes
Comment #45
benjifisherUsability review
We discussed this issue at #3240281: Drupal Usability Meeting 2021-10-08. That issue will have link to a recording of the meeting.
This meeting had more participants than the one on 2021-07-16 (see Comment #23). I am not sure whether the difference is having a different group or if the points in #25 convinced us, but this time we agree that radio buttons are clearer. There was pretty strong consensus on that.
I see that the screenshot in the issue summary (and in #27) uses the wording I suggested in #26. As I said then, there is room for improvement. The tricky part is how to describe the default revision. In #26, I suggested "original language", based on the existing text on the translations tab such as
/node/2/translations. Looking at it again, I prefer "original content" here, and the usability group agreed that is an improvement:But maybe you can come up with something even better.
The other thing I notice, looking at it again, is that in #26 I included ending punctuation. The two options are not complete sentences, so you were right to remove that.
Comment #46
dwwStill Needs upgrade path + Needs tests, but here are a few fixes per #42 and #45, and cleanup of the open questions in the summary. More coming later today...
Comment #47
dwwOkay, after a fun Slack session with @larowlan, I sorted out Kernel test coverage for the scenario in the original bug report. So that's now working exactly as expected.
Also fleshed out the Functional test coverage we already have to make sure the new knob disappears from the workflow edit form when you uninstall content_translation.
So I'm removing the generic "Needs tests" tag and adding "Needs upgrade path tests" since those are the only tests we're still missing. More cleanup of open questions and remaining tasks.
The only open question remaining is:
hook_entity_translation_create()EntityOperations::updateOrCreateFromEntity()? This seems problematic since we'll have a hard time knowing if the entity has a value from a widget or if its own override logic should kick-in.The only remaining task is: "Add a DB update for existing sites"
Home stretch now 🤞
Thanks,
-Derek
p.s. Assigning to myself since I'm working on the upgrade path now...
Comment #48
dwwThe post_update itself was easy. The upgrade path test is more yucky since the existing system/tests/fixtures/update/drupal-9.0.0.filled.standard.php.gz doesn't have content_moderation installed. https://www.drupal.org/docs/drupal-apis/update-api/writing-automated-upd... doesn't really explain how to generate the setup file to install whole new modules. 8.9.x's copy of content_moderation has some of these setup files, but I don't think we can re-use any of that.
Comment #50
dww#49 is a random fail in
core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php:185. @see #3208791: [random test failure] Random fail in LayoutBuilderDisableInteractionsTest.Anyway, here's most of an update path test. I'm kinda fumbling around in the dark here. https://www.drupal.org/docs/drupal-apis/update-api/writing-automated-upd... doesn't explain how to generate a test fixture setup file that enables a different module and its default config from a given prior state. I'm looking around for existing examples, and there’s not much to draw on from 9.3.x branch.
core/modules/layout_builder/tests/fixtures/update/layout-builder.phphas some of the necessary magic. I’m making some progress, but hitting a wall with this:Not sure how to craft the setup script so that entity type gets installed properly and everything works as expected.
Is there any easier way to do this?
Any pointers / insights would be most appreciated!
Thanks,
-Derek
Comment #51
dwwOh yeah, the patch. 😉
Comment #53
dwwIn Slack, @larowlan suggested using core/scripts/dump-database-d8-mysql.php for this. On its own, that'd create a huge new fixture, which seems like a big waste. However, I figured out a way to use that to solve my troubles:
I preemptively ran
phpcbfon the results to convert to short array syntax so that the testbot doesn't complain about CS violations.Comment #54
dwwRealized the previous patch left out another section of the diff between the two DB snapshots. I was juggling too many diffs and missed some. Since setting up this fixture is such a pain, I figured I should do a thorough / complete job now in the hope that it'll be helpful to someone else down the road. Since I was totally setup for this mess, I wanted to really finish, even though the test passes with #53 alone.
Comment #55
dwwp.s. Added a section to https://www.drupal.org/docs/drupal-apis/update-api/writing-automated-upd... about how to do this.
Comment #56
dwwWhoops. Turned off code style, but forgot to disable cspell, too. 🤦♂️ Let's try this...
Comment #57
larowlanI think option 7 has been resolved, the hook is the API core makes available for it, doing it in a widget is wrong. We can probably mark that as resolved
Here's some observations/questions
should we be resolving this here?
now there's an update hook, this shouldn't happen now right?
this is the third instance of this logic, should it live somewhere central?
this should always be set now right? we could set it in the constructor to default if not and avoid this check?
Comment #58
dwwThanks for the review!
Closing open question 7. Per Slack chat, moved the logic that implements
hook_entity_translation_create()into\Drupal\content_moderation\EntityOperations::entityTranslationCreate()and stuck it right next to the existingentityTranslationDelete()method. Movedcontent_moderation_entity_translation_create()up in content_moderation.module to be next tocontent_moderation_entity_translation_delete(), too.Now for the review points:
I did some more local digging and discovered that on the first page load after a cache rebuild, inside
ModerationStateWidget::formElement(), this is returning the wrong bundle ID:$this->fieldDefinition->getTargetBundle();. If I inspect the value, on the first load, I get "page" (wrong), which is why the lookup for the workflow fails and we access$configurationeven though it's empty. Next page load, it's back to "article" (correct), and remains that way.This is all true in the clean 9.3.x branch, too. Totally unrelated to this issue. In Slack, @Sam152 pointed us to #3077876: content_moderation_entity_bundle_field_info overrides the target bundle setting on the field definition for all bundles, which sounds like the underlying bug. No followup needed.
Regardless, I removed the
@todoand simplified the code to use$entity->bundle()instead. That always works (and is more clear / direct to express what this code is trying to do).WorkflowType/ContentModeration::defaultConfiguration()and it works again. That seems cleaner/better than theisset().So, potentially NW for the Rule of Three refactoring, but uploading a new patch for now, in case we're cool leaving this as-is.
Thanks again!
-Derek
Comment #60
dwwHah, whoops. #58.4 breaks the upgrade path test, since that default setting now shows up in the "before" state, even though the setting isn't present in the DB dump fixture.
Reverted using
ContentModeration::defaultConfiguration(). Instead, to make the Kernel tests happy, I added a default fortranslation_default_moderation_state_behaviorinContentModerationTestTrait::createEditorialWorkflow().Is that legit?
Or is #58.4 more correct, and we shouldn't worry about an upgrade path at all? 😢
Thoughts?
Thanks,
-Derek
Comment #61
dwwIn Slack, we discussed this more. The last few patches are Doing It Wrong(tm). 😉 I’ve got a new patch to upload here that I worked on while offline sitting on a plane. just need to get my laptop online sometime this evening to upload it. Stay tuned....
Comment #62
dwwPer @Sam152 in Slack, #2543150: Document consequences of contrib changing config schema without core's API supporting config version tracking says we need both an update path and to support things if the value isn't there. So I restored the isset() and friends, removed the default config, and got everything happy again.
Comment #63
larowlanI've gone over this again.
And yes, Sam is right about needing to handle when the config isn't set - we do that for views because e.g. they could be broken in a profile.
However, in this case we can set a default value in the defaultConfiguration method so I don't think it applies. But I'm not going to split hairs on it.
This looks ready to go.
Comment #64
alexpottI think if content_translation is not installed this will change the value from 'current' which has been set by content_moderation_post_update_set_translation_default_moderation_state_behavior to a NULL.
This also saves us when the configuration has been saved again when the content translation module is not installed.
I think the behaviour here is very awkward and needs thinking about. The best thing to do will be to set the current value as a hidden value on the form when content translation is not installed.
Comment #65
dww@alexpott Thanks for the careful review - good catch. Working on fixing 64.1 + 2 now.
Re: 64.3 - yeah, I don't know how this feature could entirely live in content_translation without much deeper API changes.
core/modules/content_moderation/src/ModerationInformation.phpgives us no means to try to influence the outcome ofgetOriginalState(). Adding an alter hook in there seems pretty strange. So yeah, I think we have to leave this basically as-is...Comment #66
dwwFixes 64.1 + 2, and adds a little test coverage of it. The test-only is just the new test coverage, without the fix, and a drupalci.yml change to only run the failure test. Should die like so:
Comment #67
dwwI don't know why I mess up
drupalci.ymlchanges so frequently, trying to save bot cycles. 🤦♂️ Anyway, I don't want to fiddle with it now, since it'll just confuse the bot if I upload the test-only here. But the output above is from a local run. If you don't trust me, you can apply these two:And then run it locally yourself:
Comment #68
dwwp.s. This is why: run-test.sh doesn't like the
=here. This should be:Anyway, still don't think it's worth a whole new pair of patches here, but happy to upload them if we need. I believe this is RTBC, but I'm obviously not qualified to change the status.
Comment #69
darvanenThis is handled as an isset() call in the ContentModeration plugin, why do it that way there and this way here? I personally find the logic easier to follow in the plugin version. I think this may be what @alexpott meant by awkward behaviour?
Nit(?):
$entity->isNew()is self explanatory, this variable name is not completely so. As far as I can tell, the workflow is the new thing here, could we call this variable$new_workflowor similar (to improve DX)?Same as #2.
Comment #70
dwwThanks for the review, @darvanen!
#69.1: Ahh, good point. That's a hold-over from the @todo weirdness I removed in #58.1. Now that that's actually working as expected, the code doesn't need to be doing it the way it was, and the
isset()is sufficient. Fixed here.Points 2 and 3 I'm a little reluctant to change. It's not a "workflow" that's new here. It's either that the entity itself is brand new, or it's a new translation of the entity, and the workflow is configured to treat new translations as if they're a new entity and start off in the default workflow state. I know
$is_newisn't the most self-documenting variable name, but$new_workflowwould definitely be misleading. Maybe$treat_as_newor something, but that seems a little clumsy, too. Given that neither @larowlan nor @alexpott flagged this part, I'm inclined to leave it alone.The "better" solution would be to consolidate these checks per #57.3. Per later discussion, there's no particularly good place to share it, since each of the 3 is at a different layer of this system (field widget plugin, workflow type plugin, ModerationInfo service). But I just looked again, and realized that both the field widget and workflow type plugins have an injected copy of the
ModerationInfoservice. So if we wanted to, we could add a new public method on that service, something likeModerationInfo::treatAsNew(EntityInterface $entity)or something. But I'm not sure a new public API for this is worth adding. I'll await confirmation from a core committer on that before I do it.Meanwhile, since it's a new patch, here's a (hopefully) proper fail patch for the new test in 66. 🤞
Comment #72
dwwYay, well that's at least progress. 😉
https://www.drupal.org/pift-ci-job/2221701
The failed assertion is here:
So yay, the test fails if you don't have the fix for #64 1+2.
Comment #73
darvanenProgress is awesome :D
Fair enough re the variable naming, happy to let that go as it seems to come down to a personal interpretation.
I have just one request before I turn this green?:
Can we spread this over 4 lines like in the plugin? I think it's technically allowed to be that long (216 characters) as it's in an if statement but it doesn't really set a great precedent for readability in my opinion.
Comment #74
dwwConfirmed with @larowlan via Slack that the proposal at the end of #70 for adding something like
ModerationInfo::treatAsNew(EntityInterface $entity): boolwould be unwelcome API surface. We've hit the rule of 3, but it's for a relatively small variable initialization, so it's not too much of a burden to duplicate the code in the 3 places we need it. Still happy to add it if needed, but it does seem like premature API-ification to both of us.Therefore, it was worth fixing the readability of one of the copies of it per #73. Done here.
Anything else?
Thanks!
-Derek
Comment #75
darvanenMakes sense.
Not from me!
I don't expect the test to fail and I have to leave early today so I'm gonna switch this now, if there is a failure somehow the bot will handle it :)
Comment #76
dwwThanks! Very happy to see this RTBC again. Saving credit for @darvanen for the useful reviews.
Adding 'content moderation multilingual bug' as an issue tag I found when looking at related issues.
Comment #77
joel_osc commentedTested patch against 9.1 and it works perfectly. Thank you @dww for the amazing effort on this one and all the others that contributed!
Comment #78
hctomI just came across this patch and applied it to 9.2.6 without errors! Works perfectly and does exactly what we need. Thanks for all your efforts
Comment #79
dwwre: #77 and #78: Thanks! Glad it's working well for you both.
Weird that the bot determined that the patch no longer applies but left the status as RTBC.
Anyway, this needed a trivial re-roll after #3222251: [November 8, 2021] Replace all isset constructs with the null coalescing operator landed. No interdiff, but here's a raw diff of the 2 patch files to see the context changed. Leaving RTBC.
Comment #80
dwwFYI: bot weirdness is here: #3047797: Patch failing to apply should reset RTBC status to Needs Work
Comment #82
dishabhadra commentedI tried to apply patch #79 for the Drupal 9.2.7 version but the patch was failing.
Re-rolled the patch for the Drupal 9.2.7 version.
Comment #84
dwwRe-uploading #79 which is still RTBC.
Comment #86
spokjeBack to RTBC per #84 after #3255836: Test fails due to Composer 2.2 solved the unrelated test failure.
Comment #87
sam152 commentedThis seems really close. I love how none of the existing test cases seemed to require modification to get this to pass, adds a lot of confidence in the BC layer.
Not going to change the status, because a committer might disagree with these observations:
I'm not sure if this has been explained or discussed in this issue, but feels like this should have an explanation in a code comment.
I can't make sense of why this is required, if the implementation of `getInitialState` has already been modified to be aware of the configuration flag?
Not a huge fan of calling this variable "is_new", when it represents "is new or is a new translation and the new translation behavior has been assigned to default". Couple of additional booleans here would probably clean this up a bit.
This also applies to where this has been copied to. (Also this is more a preference thing, but I would just bite the bullet and put some of this stuff behind methods, being used in two places seems like enough of a signal to create a function to me)
Wonder if we can just put this tricky isset/default comparison behind a isTranslatioNBehaviorDefault/isTranslationBehaviorCurrent or perhaps `getTranslationBehaviour(): string` on the plugin class.
Also feels like current/default are candidates for constants (or an enum, if we have that option).
I'd love to see a test case where "default_moderation_state" was set to both published default and unpublished non default states that were neither "published" or "draft".
Comment #88
spokjePatch 79/84 needs a reroll.
While you're at it, you might want to take #87 in to consideration.
Comment #89
dwwThanks for the reviews and updates! I'll do a reroll, then address #87. Stay tuned...
Comment #90
dwwHere's a straight re-roll for 9.4.x. Had a fairly trivial conflict to resolve. Interdiff is confused, but here's the raw diff. Leaving assigned to myself to work on 87.
Comment #91
dwwRe #87: Thanks for the careful review! Much appreciated.
ModerationInfo::treatAsNew(EntityInterface $entity): boolor equivalent.??nicely to make it more simple and clear. Anyway, not changing this until a committer says so.However, when I did this, the test was failing like so:
I went digging, and discovered something right under our noses (in the patch context). The current behavior (unchanged by this patch) when something is supposed to use the "current" / legacy behavior is not what all of us assumed it was: "inherit the current state of the original language". It's actually this:
🤯 Whoops. So the BC layer is implementing the wrong UI to describe the legacy / existing behavior. It's not "Current moderation state of the original content", it's something like "Set to 'published' if the original language is currently in any published state, otherwise 'draft'." That's of course horrible UI text. 😉 We'll have to figure it out.
Not sure where to go from here. Some possible approaches:
A) Rename the UI for the new "current" setting to more accurately explain the current / legacy behavior. If we just keep 2 options, I'd strongly consider naming this one "legacy" not "current".
B) Provide 3 options in the new workflow plugin settings for the behavior:
C) Other? Not sure...
I don't think we can plow forward with the current disagreement between the UI text and the actual behavior. I do think we're going to have to keep supporting the current behavior, so we're going to have to figure out what to call it and how to explain how it works to site builders configuring workflows.
Thoughts?
Thanks/sorry,
-Derek
Comment #92
sam152 commentedThe analysis in #91 sounds pretty spot on to me. I have no idea off the top of my head if/why/how that code path is entered for new translations, but I do agree it probably needs to stick. "Legacy" does seem to describe it, given it's not logical or intended.
Comment #93
dwwWhoops, typo in the new test comments.
sh ./core/scripts/dev/commit-code-check.sh --branch 9.4.xis now passing locally. Let's try that again...Comment #95
dwwSam and I continued to discuss in Slack. We agreed that #91.B is too much scope creep. So I opened #3257186: [PP-1] Add a 'Current' option to the workflow translation_default_moderation_state_behavior setting to track adding 'Current' as an option to this behavior setting once this issue lands.
Meanwhile, here's a preliminary stab at changing the new setting from 'current' to 'legacy'. Here's my very sad initial UI text:
Comment #96
darvanenIs there any way to get the "Default moderation state" label to include what that default is set to?
Are Published and Draft really the only possible options for the legacy behaviour?
Comment #97
dwwRe: #96 question 1:
Although the wording of the new setting is changing, its placement in the form is not, so the context you need is right there.
Re: question 2: Yes, the existing code in
Drupal\content_moderation\Plugin\WorkflowType\ContentModeration::getInitialState()does this:Re: #92:
Git blame points here:
So #2856967: Allow admins to select a default entity moderation state was the last to touch this code. However, digging deeper into the Git archeology, that was just refactoring existing logic. This legacy behavior has been touched by all these commits:
But it was actually introduced by commit 469003db for #2817835: When enabling moderation apply a relative state. I skimmed that issue, but didn't see any clear / direct discussion of this behavior as being intentional and desirable.
Not sure that was all that helpful of a history dive, but there you have it. 😉 Maybe that'll help jog @Sam152's memory, but probably not.
Anyway, I think the next steps are to bikeshed the new UI text again. Maybe the UX team would be willing to re-review this. But if anyone has suggestions for improvement over what's in #95, please post them here.
Thanks!
-Derek
Comment #98
darvanenThanks for the very detailed answer!
I was just looking for potential logical issues with the wording and those have been blown out of the water. I think the wording is fine as is.
Comment #99
dwwRe: #98: Thanks! But I'm sure we can do better. 😉
Updating the summary:
I hope this is now clear enough for a UX team re-review.
Comment #100
dwwOfficially requested UX team re-review at #3257468-2: Drupal Usability Meeting 2022-01-14. If anyone is available "tomorrow" "morning" (depending on your TZ) to attend the UX meeting and present this, that'd be great. Details in the linked agenda issue. If not, hopefully they now have enough to go on.
Comment #101
g-brodieiWe discussed the UX wording for the option at #3261138: Drupal Usability Meeting 2022-02-04. That issue will have a link to the recording.
Proposed wording when content translation & content moderation are enabled
Initial moderation state when translating existing content
1) Default moderation state
2) Published if existing content is published, otherwise Draft
Comment #102
benjifisherI attended the Usability meeting today.
I am setting the IS to NW for the change recommended in #101.
In #101, we suggest using the phrase "existing content" in the label and then in the second option. We think this will help clarify the second option. If you do not like "existing content", then a similar phrase should work just as well.
Our suggested text removes "Legacy behavior: either". We feel this is not actionable information. That it, it does not help the site builder decide which option to choose. Even after removing this bit, I think we managed to make the text shorter and more explicit.
Some people felt that, even though the new option comes directly below "Default moderation state", the first option would be clearer if you added the currently selected value, like "Default moderation state (Draft)". Consider that an optional enhancement.
Some felt that "new content" in the existing help text is not enough to clarify the distinction between the two options. Perhaps, when Content Translation is enabled, we could add a line to that help text. I guess something like, "Here, 'new content' does not include a new translation of existing content. For that, the next option applies." But we did not come up with a definite suggestion, so we can leave this as is for now. My personal opinion is to follow the usual rule that "less is more".
For the record: the participants at today's meeting were
benjifisher, g-brodiei, rkoller, worldlinemine, AaronMcHale, mariaisp, victoria-marina, shaal, tmaiochi
Comment #103
aaronmchaleAlso to note that for the second radio button, the terms "Published" and "Draft" are italicized to indicate that those would be substituted with the labels for the Draft and Published states.
Comment #104
dwwThanks everyone for the review and vastly improved UI text suggestions!
Implemented here. Also a fresh rebase to 9.4.x. Confirmed this applies cleanly to 10.0.x. New screenshot in the summary. Also closed out open questions and updated remaining tasks. RTBC is all that's left. 😉
#101 looks super clean and simple. Way better than #95. Thanks!
Re: #102: All the suggestions for improvement are great and make sense. Thanks for explaining the reasoning so that we can all learn and benefit from the thoughtful discussion.
#96 already proposed this, and I replied in #97 (first 3 points) why I think that shouldn't happen here, if at all. 😉 I believe it's scope creep for this bug fix, and belongs in a follow-up feature request. Not going to tag for "needs followup" but if someone is inspired to open it as a child issue, please do.
Also happy if anyone wants to open a follow-up to conditionally modify the existing help text for the Default moderation state setting when content_translation is enabled. I agree with less is more, and I don't think it's needed, but others might disagree. The new Initial moderation state when translating existing content setting only appears if content_translation is enabled at all, so hopefully it's clear enough as-is.
#103: Thanks for clarifying. That's what #95 was doing already, so I assumed y'all meant to keep that behavior.
Ready for a (final?) review and RTBC.
Thanks again, everyone!
-Derek
Comment #105
dwwWrong # for the files, sorry about that. ;) Fixing a few things in the summary.
Comment #106
sam152 commentedThis does add some complexity to a few areas of CM and translations have been one of the harder things to deal with, especially given the lack of active maintainers with deep understanding of the translation system.
However, I think given the number of times this headache has been reported, the number of followers the issue has, the diligence in preserving BC, good automated testing, pivoting and following up UX, the extremely detailed issue summary, the fact that it'll only appear for folks doing translations.. this is ready for commit and should hopefully make a lot of existing and new users quite happy.
Massive effort, especially dealing with a handful of extremely complicated subsystems, nice one @dww.
If committed, the CR might need a tweak, but we should definitely not block behind that.
Comment #107
quietone commentedChanging version to 10.0.x since this needs to be applied there first. Also, starting a test on 10.0.x
Comment #108
dwwThis update test needs to use the new fixture from #3263886: Copy drupal-9.3.0.bare.standard.php.gz and drupal-9.3.0.filled.standard.php.gz from the Drupal 10 branch, then it should pass on all the branches. I’ll work on it after breakfast. Stay tuned.
Comment #109
dwwWork got busy after breakfast 😅, but here we are...
Comment #110
darvanenGiven it's a one-line change that exactly meets the reason for putting this to NW and it's passing, this is now back to RTBC.
Comment #111
alexpottThis is really odd. Firstly we should never be concerned with sites that haven't updated their config. Runtime code should assume that all updates have been run. We don't support running code with outstanding updates.
Secondly, I think that this means that the default value for this is legacy - so if you start fresh and enable content moderation and then at some point in the future enable content translation by default you'll be in the legacy mode. I'm not sure that that makes sense.
Looks like this might need updating.
Comment #112
larowlanI think this was in the same vein as when we add a hook views presave for views config changes.
i.e. there maybe configuration in an install profile that is invalid.
Comment #113
larowlanSee #62 for where that came about
Comment #114
sam152 commentedYeah, I've gotten that feedback in the past, but it often gets questioned when implemented, so I've either misunderstood that feedback or it's not widely accepted as a problem.
I could however definitely conceive of a bunch of workflow configs floating around in various forms that aren't being affected by an updb process. For any site installed after this issue is committed, the updb won't impact them and importing those config files will leave those installations broken.
Comment #115
dwwRe: #111:
1. I don't know what to say. 😉 I've implemented what @larowlan and @Sam152 asked for, and included links to the various places where what they're asking for was agreed / decided / etc. I truly don't see the harm in having this code be resilient in the face of the unexpected. If you want me to intentionally fragile-ify the code and make it so that things will just fatal error if folks don't update their config, so be it. But a. ugh, I wish everyone was on the same page about this in the first place and b. I don't understand what we gain by making this more fragile. I don't know that it would significantly simplify the code. It's already written and tested to be defensive.
2. Yeah, my heart truly sank when I started working on #109 and remembered what a massive effort it was to get this update test working in the first place, only to have #3261486: Remove core updates added prior to 9.3.0 and adjust test coverage remove the fixture it depended on. 😢 I was being a bit of a cowboy, and instead of doing it all over again, I just made the 1 line change and uploaded it to see what the bot said. 😅 To my great delight, it all just works. So the fixture I built to content_moderation-ify a standard install works just as well starting from 9.3.0 as it did from 9.0.0. 🎉 Phew! Therefore, here's a patch to update the comments accordingly. 😉
Back to NR. I don't want to start writing any code for 111.1 until there's agreement on what y'all want.
Thanks,
-Derek
Comment #116
rinku jacob 13 commentedReviewed and tested the above patch with 10.0.x. Adding screenshots for the references
Comment #117
dww@Rinku Jacob 13: The summary already has accurate and updated screenshots. Removing credit for attaching duplicate screenshots.
If you want to be credited in an issue for attaching screenshots, I suggest:
If you do neither, you're not really helping move the issue forward with this activity, so core maintainers / committers are likely to remove your credit.
There's a "Needs screenshots" tag that you can work from if you need help finding issues for step #1. 😉 If you attach screenshots to one of those issues, and update the summary, you can then remove the tag. That's actually helpful to getting issues marked fixed, and worth granting credit for.
Thanks,
-Derek
Comment #118
smustgrave commentedTested this out locally on 10.0.x (not sure if this will move to 10.1.x)
Applied cleanly
Followed the testing steps written in the description and verified it worked.
Side note that is one of the best issue summaries I've read yet. Super clear every section.
I see there are no remaining tasks so I'll mark it RTBC.
Thanks!
Comment #120
smustgrave commentedCan I move back to RTBC? Just updated 9.3 to 9.4 not a big change.
Comment #121
smustgrave commentedComment #122
james hawthorn-byng commented@smustgrave the latest patch doesn’t apply to my 9.4.7 via composer because of the change to the .gitignore file.
Once I removed that it applied fine. I might be wrong, but I don't think this module should be affecting that file?
Comment #123
smustgrave commentedit should not.
Comment #124
shiraz dindarHere's the patch from #120 with the gitignore changes (as mentioned in #122) removed. It now applies properly to the latest stable Drupal as of this is writing.
Comment #125
joseph.olstadwe've been using patch 109 with D10.0.10 and previous Drupals for a while now
has tests and passes tests. What is left to do on this?
Comment #126
smustgrave commentedDid a light review
Test coverage appears to be there
Post update calls for new config.
But patch 124 does not apply to latest development branch.
Also change record will need slight updating for branch version.
Comment #127
joseph.olstadstraight reroll of 124 for 11.x
looks like the conflicting test changes need some attention
Comment #128
mahdePatch #124 doesn't apply on Drupal 10.2
Comment #129
mahdeI appreciate if someone can create the updated patch for Drupal 10.2.
Thanks!
Comment #130
mahdeReroll of 124 for 10.2.x.
Comment #131
artemboiko@mahde, thanx.
Looks like patch 128 works fine for 10.2.1
Comment #132
josuerThank you, @mahde!
Patch #128 works fine for 10.2.2 - PHP 8.2
Comment #133
mdziedzic commentedThanks @mahde! Can also confirm that #128 works for 10.2.3!
Comment #135
joseph.olstadExperiencing some sort of strange issue with the remote so I'll just push up the updated 11.x patch.
Comment #136
smulvih2Missing two
usestatements in content_moderation.module, which was causing error on node create forms.Comment #137
frevda commentedUpdated patch for 11.3.x.