Problem/Motivation
At the moment, the Workspaces module can not be installed if Content Moderation is also installed, see #2971699: Content Moderation and Workspace don't work together.
After #3027598: Omit workspaces entity presave and predelete hooks for internal entities is done, there is very little work needed to make them compatible.
Proposed resolution
Remove the 'latest-revision' link template when both Workspaces and Content Moderation are installed, because Workspaces always shows the active revision on the canonical page of an entity, so the latest revision tab is not needed.
Make Content Moderation skip and pre-save logic if an entity is syncing, for example when it is being published from a non-default workspace to the Live one.
Remaining tasks
Add test coverage.
User interface changes
The Latest revision tab will not be shown when both Workspaces and Content Moderation are installed.
API changes
Nope.
Data model changes
Nope.
Release notes snippet
Workspaces and Content Moderation are no longer incompatible and can be installed and used together. Advanced workflows like moderating an entire workspace are possible as well.
Comment | File | Size | Author |
---|---|---|---|
#39 | interdiff-39.txt | 5.71 KB | amateescu |
#39 | 3037136-39.patch | 58.96 KB | amateescu |
#34 | interdiff-34.txt | 7.91 KB | amateescu |
#34 | 3037136-34.patch | 58.62 KB | amateescu |
#29 | interdiff-29.txt | 2.83 KB | amateescu |
Comments
Comment #2
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThis is all that's needed after #3027598: Omit workspaces entity presave and predelete hooks for internal entities gets in.
Comment #4
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented🔧 I wonder if this guard should go into
ModerationHandler
instead, so you could potentially undo this behaviour if you had an entity type that was special and made special use ofSynchronizableInterface
somehow.❓ Does this neglect using content moderation in the default workspace? Is the expectation that you could use all the normal CM features if you were in the default workspace?
❓ This sounds to me like you would be able to actually transition the workspace entity itself through a moderation workflow, but I don't think that's actually what this patch unblocks right?
FWIW, I have thought about this a bit and I imagine if this was what you wanted it you could create a new workflow type and instead of options like: default revision, published status etc, each state would track information like "does reaching this state deploy the workspace". I'd be interested in contributing to this if it came up in the roadmap.
Comment #5
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedPostponing on #2803717: Allow 'syncing' content to be updated in content moderation without forcing the creation of a new revision for now.
Comment #6
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@Sam152, re #4:
1. Since this is now handled by #2803717: Allow 'syncing' content to be updated in content moderation without forcing the creation of a new revision, I've removed that hunk from the patch.
2. It kind of does :) I suppose the assumption that "if you're using workspaces, you shouldn't use CM on the default workspace" is too big for core to provide by default? Can you think of another way to disable the latest revision route/tab when you are in a non-default workspace? Maybe returning access denied
\Drupal\content_moderation\Access\LatestRevisionCheck
?3. This patch aims to unblock using Workspaces with CM in general at the UI and API level :)
That sounds like a useful workflow type. Another proposal after a discussion with @catch a while ago was something like: "you can only publish a workspace if all its tracked entities have reached a published CM state". So the default workflow for WS + CM that will provided by core is very much up for discussion, but the capabilities introduced by this patch shouldn't be blocked on that IMO.
Comment #7
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedRe: #6.3, if the incompatibility check is being removed, how does the current approach work by default though? It seems like after stable, it would be something that's tricky to change in core and possibly something that should have some functional testing built around.
Comment #8
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@Sam152, with the current patch we're introducing the ability to moderate entities in non-default workspaces without any restrictions or extra requirements, just like they can be moderated without the Workspaces module :)
#2803717: Allow 'syncing' content to be updated in content moderation without forcing the creation of a new revision is in, so this is no longer postponed.
Comment #9
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI was primarily responding to this part. Should that question be answered in detail before removing the
hook_requirements
?As a random thought, I wonder if we could test this by extending a CM test and switching workspaces in the setup method?
Comment #10
dixon_In general CM is useful within an unpublished workspace if you manage large volumes of content in a workspace (we are looking to use it like that where I work). However, in order to make this work properly we would want to stop workspaces from being published/merged until all content is in a published state. The CM workflow within the workspace would ensure that each individual content has gone through review accordingly.
As mentioned in another comment above, I can also see a use case where you attach a workflow to the workspace entity itself. In our initial wireframes and mockups for workspaces we actually had multiple states on entire workspaces such as "draft" and "needs review".
Comment #11
blazey CreditAttribution: blazey at Amazee Labs commentedComment #12
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedHere's the code needed to prevent the publishing of a workspace that has content in an unpublished moderation state. Still needs some UX improvements and test coverage though :)
This patch is based on #3062434: Track the workspace of a revision in a base field and convert the workspace_association entity type to a custom index because I wanted to use the new
workspace_association
service to be sure that there aren't any problems introduced by that patch.Comment #13
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedStarted writing integration test coverage which does what @Sam152 proposed in #9: extend
ContentModerationStateTest
(which seems to provide the most comprehensive API coverage for CM) and make it work in the context of a non-default workspace.There are a few failures that require some thinking and decisions. For example, CM synchronizes the "defaultness" of the moderated entity with the one of its content_moderation_state entity. But, as the failure in
testRevisionDefaultState
shows, this synchronization no longer works because a revision saved in a non-default workspace can *never* be the default one, so we need to figure out how to work out this difference in behavior. Maybe it's related to the issue posted above by @blazey ..Comment #15
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedDiscussed with @Sam152 and, at least for
ContentModerationStateTest::testRevisionDefaultState()
, we should update the test to check whether the revision "defaultness" of an entity and its moderation state are in sync instead of dancing around revision numbers.Also, we need to introduce a new
entity_test_revpub
entity type so we don't wreak havoc in other unrelated test classes.Comment #17
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLet's get most of the failures out of the way. Keeping at NW because at least the new integration test will still fail.
Comment #18
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedAnother WIP patch: updated
\Drupal\Tests\content_moderation\Kernel\ContentModerationStateTest::testContentModerationStateRevisionDataRemoval()
to test a scenario that can also be completed in the context of a workspace, where we can only have pending revisions.Comment #19
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedDiscussed this issue with @catch today and we agreed on the fact that, in the context of Content Moderation working together with Workspaces, the "default revision" is the "latest workspace-specific revision", which is consistent with the way that Workspaces handles default revisions when there is active workspace.
Comment #20
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedResolved a @todo and improved the UX a bit by sending a message to let the user know why a workspace could not be published.
This should be ready for final reviews now :)
Comment #21
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFollow-up review, sorry if I repeat things that have already been decided/discussed.
Is this tested anywhere?
Could be a nice place to return early and reduce the indent of this whole function by 1.
Is this comment in the right place? Finding the workflows is done later on and by the looks of it the chunk as a whole is finding states, not workflows.
Could this chunk be simplified to:
array_filter($workflow_type->getStates(), function($state) { return !$state->isPublished();});
and then you can simply deal with a state object for the rest of the method can call $state->id() later on?Does
range(0, 1)
speed this up further?Hm, what about content that is both unpublished but default, like the archived state? Does the check need to be for "non default" instead of "unpublished"?
If I archived some content in stage, I probably would want that archival to propagate when deploying the workspace.
All the refactoring in here is great, thanks!
I think since workspaces alters the default behavior of the canonical route, it's probably also correct for workspaces to clean up CMs interface for viewing pending revisions.
Needs issue link?
Could add a bit of general utility here and return
"workspace_access_test.result.$operation"
.This is kind of mind-bending, but I love it! :D
Does this still incur some install cost? I would be fine with moving them into another test class in the parent.
Comment #22
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@Sam152, thanks for the review!
Re #21:
Published
state toReady for publishing
. This point is still TODO.Comment #23
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedComment #24
blazey CreditAttribution: blazey at Amazee Labs commented@Sam152
@amateescu
That sounds a bit like "Archiving doesn't fit into our current implementation so let's hide it". The first sentence is also rather strong and isn't followed by any reasoning. Would you mind elaborating on that?
Comment #25
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedNot really, it's just how I thought it should work at the time, and the current implementation needs only a one-line change to switch to that model :)
After giving it more thought, I believe that @Sam152's suggestion makes perfect sense, so here's a new patch for that. Also added full test coverage for the integration between CM and WS, and addressed the 'Published' -> 'Ready for publishing' moderation state rename for the Editorial workflow that is installed by the Standard profile.
Comment #27
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedFixed that.
Comment #28
blazey CreditAttribution: blazey at Amazee Labs commentedNice! Moving from the publishing status to the default revision state makes all the sense. A few comments:
This variable now stores non-default-revision states.
Similar to the one above, the workspace cannot be published because it contains X items in a (non-default? temporary? intermediate?) state.
A general question at the end to all participants. This is a real-world use case we're facing time after time. Multinational organizations often want to have their content structured into markets with inheritance. For instance, Quebec gets the same content as France but with the ability to customize it, i.e. hide existing entities, create new ones (such that won't be available in France) or just change pieces of the source items. This seems to be the flow that some products on the market, ones with very expensive licenses, are providing. If, as Dries said, we want Drupal to be a good alternative to that kind of products, we need to make it possible to have more than one active workspace. Is this something that we plan to support?
Comment #29
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThanks for the review :)
1. Oops, fixed.
2. I thought about that quite a bit before posting #25 and my conclusion was that "unpublished" is still a good term to use for this user-facing string. My main consideration point was that even we're now technically considering only non-default moderation states, those states are usually also configured to create unpublished revisions, like Archived.
3. Yeah, there's no great choice here as well. My impression is that when CM is used together with Workspaces, the 80% use-case is to moderate content inside a workspace, so that's what I think we should "optimize" the label for. In any case, this is just a default label provided by core, site builders are free to rename it to whatever makes sense for their workflows.
That's the functionality we want to provide in #3062486: Add the ability to create sub-workspaces in order to enable the dev -> stage -> live workflow for content, and, with that patch, the concrete use-case you mentioned can be accomplished in (at least) two ways:
- Quebec can have France as the parent workspace, so it inherits its content by default, but it's also able to modify or add new content
- Quebec and France can both have a common parent workspace, allowing them to inherit its content but also to diverge in any way that might be required
Comment #30
blazey CreditAttribution: blazey at Amazee Labs commentedThere's one missing piece there. If we want to be able to do market-level content moderation, then we need some kind of an indicator saying which revision is the default one in the context of a workspace. Example:
The Quebec workspace is used to serve content for that region. There is a published node which is going to be changed, so the editor creates a new draft which should go through the moderation flow. That last part is something we can't do now with the API.
Entity::loadMultiple
, the entity query and evenModerationInformation::getDefaultRevisionId
all return the most recent revision, an unaccepted draft in this case.It is counter intuitive, especially for the last method which name clearly states that it should return the default version. Even if we want the first two to return pending revisions, we could at least make this information available in the ModerationInformation service. The data is there in the database but the only way to retrieve it now is with an ugly db select. If altering
getDefaultRevisionId
is not a good idea then maybe we could add a new method just for that?Comment #31
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedReviewing the inter.
The new coverage is great, but it seems like it would be valuable to expand this to at least go through at least one test scenario from the UI.
This style of assertion doesn't guarantee an exception is thrown. The test still passes with the following diff applied:
Hm, this seems a bit invasive and unnecessary. If the use case is split 80/20 I think some valid alternatives are: allow people to simply update and customise labels as they see fit or, ship a "Workspace editorial workflow" with updated labels created specifically for folks with that use case in mind.
As a fallback, I think it should at least check that it isn't changing a state label that a user has already decided to customise.
Comment #32
BerdirIt's really hard to meaningfully review this issue for me because I don't have practical experience with workspaces and don't really know the code. Will have to try it out.
This does feel a bit strange though.
Usually access is more or less directly tied to the current user, but the way it is used here is more about validation I think and doesn't depend on the user. So normally, entity operations (like save and delete) don't enforce access), we leave that to the code calling it. e.g. this could be annoying for drush commands that want to publish a workspace combined with custom access checks.
I didn't fully understand the publish access code but it feels more like validation or a different kind of conflict check like the one below? Maybe there could be a separate hook/event for that?
Just thinking out loud.
Yeah, also not sure about this, even if the 80% use case is to do this within a workspace, it's still wrong then for the other case. Might need to be more dynamic? But then it's hard to configure. Not sure :)
Also wondering how this works exactly with non-EN sites (single and multilingual), because if nothing else, this label won't be identified as translatable. And if the site uses non-EN as the default language, it might be translated already and you'd set it back to an EN label.
Comment #33
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFWIW beyond #31 I didn't have any additional feedback, so +1 RTBC if @Berdir's feedback is addressed.
Comment #34
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@blazey, re #30: keeping track of a default as well as a latest revision inside a workspace sounds really hard, and IMO outside the scope of the core module. The use-case you mentioned could be accomplished by having a parent
Quebec - Live
and a childQuebec - Editorial
workspace.@Sam152, re #31:
1. Added functional test coverage :)
2. Fixed the assertion style in that test.
3. As discussed, I've removed this part from the patch and I'll open a followup issue for it.
@Berdir, re #32:
1. This was discussed a lot with @alexpott, @catch and @chx, and an access operation seemed like the least worst option, for the following reasons (as far as I can remember them):
It's true that the scenario of publishing a workspace via Drush when user-related access controls are in place would be a bit problematic, but I don't see any way around it..
2. Took out that part of the patch, as mentioned above :)
Comment #35
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedHere's the followup for the Published state label: #3085747: Consider improving the default editorial experience when Content Moderation and Workspaces are used together
Comment #36
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThis looks good to me, #32.1 seems asked and answered if it was a decision discussed by @amateescu and other core team members.
Comment #37
plachThis looks good to me, thanks!
Aside from a few minor nits, the only thing that gave me pause is the fact that both CM and WS have a soft dependency on each other: given that CM has to know about WS but not viceversa, maybe we could move all the new tests and the code unsetting the
latest-version
template to CM to fix this.Nit: can we use
WorkspaceInterface $workspace
here?This is confusing :D
As mentioned above: could we move this to CM? I'm wondering whether we could even change the logic in
LatestRevisionCheck::access()
code to further/only check that the latest revision is not also the default revision, regardless of it being pending. This would address WS implicitly without having to do a module exist or check that the entity type is handled by WS.Nit: this is not used.
Why switch to "own"? Is this just making the test stricter?
As mentioned above, could move this and
WorkspaceContentModerationIntegrationTest.php
to CM?Comment #38
plachComment #39
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThanks for the review!
Re #37:
The problem with doing it only in the access check is that the
latest-revision
link template would still be registered, and there's a lot of code in CM that's doing stuff conditionally based on the existence of that link template, so it's better to not have it in the first place.If we want to do the module exists and workspace-supported check in CM, it means that we'd need to inject the workspace manager conditionally somehow into
\Drupal\content_moderation\EntityTypeInfo
.Based on the two points above, I think it's easier to leave that conditional logic in WS :)
Comment #40
plachDiscussed #39.3 quickly with @catch, who didn't review the code: we agreed that mutual soft-dependencies are not ideal, however, since this an important step towards making WS stable, we're fine with further untangling happening in a follow-up, especially because
EntityTypeInfo::entityTypeAlter()
is not assuming the presence of Content Moderation explicitly. From a theoretical POV any module could provide thelatest-version
link template.Aside from that, the only tricky aspect for me is what @Berdir mentioned #32.1, OTOH @amateescu's reply is compelling and we're talking about an implementation detail on an
@internal
service, thus, should we be able to find a cleaner solution, it should be possible to implement that in a non-breaking way.Comment #42
plachCommitted 4362606 and pushed to 8.8.x. Thanks!
Comment #43
plachIt would be good to provide a CR for site builders.
Comment #44
plachSetting to NW for the missing stuff above.
Comment #46
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedHere's a CR: https://www.drupal.org/node/3087336
Leaving open for now for the two followups.
Comment #47
larowlanFixing tag
Comment #48
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe two followups needed are #3085747: Consider improving the default editorial experience when Content Moderation and Workspaces are used together (already opened in #35) and #3092472: Determine whether the soft-dependencies between Workspaces and Content Moderation can be untangled (just opened now).
Comment #49
blazey CreditAttribution: blazey at Amazee Labs commentedWe've created the Workspaces moderation contrib module that makes the editors feel like they're moderating a live workspace. It introduces the concept of a shadow workspace that is loaded instead of a regular one for users with a specific permission. Shadow workspaces are excluded from all the listings so they cannot be switched to directly. Once an entity reaches a default revision state in the shadow workspace it is copied over to the real workspace.
The module is still a prototype. See this test for an example.
Comment #50
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@blazey, that's a wonderful idea! I opened #3095414: Consider adding the concept of temporary workspaces to generalize the concept of temporary workspaces in core.
Comment #52
timmillwoodThis is causing an issue with the contrib module Multiversion which defines an entity type "workspace", therefore the content_moderation_workspace_access() hook gets called.