Problem/Motivation
Follow up to #2844594: Default workflow states and transitions.
Currently we have a workflow config entity type and WorkflowType plugin. The schema of the workflow entity is such that each plugin may define the schema of the settings stored for each entity:
workflows.workflow.*:
type: config_entity
label: 'Workflow'
mapping:
...
settings:
type: workflow_settings.[%parent.type]
label: 'Settings for workflow type'
These settings hold the states and transitions the workflow will have, as well as any other bits of config the specific workflow type will need to store.
Right now, there are several setters and getters on the workflow entity which assume the structure in the settings. This is incorrect because this structure isn't enforced anywhere and shouldn't be relied on.
Proposed resolution
Instead of the workflow entity changing the workflow settings, all methods required to set/get workflow concepts like states and transitions and additional associated settings should be encapsulated in WorkflowTypeInterface. That way the schema of the states and transitions is internal to the plugin and exposed only via an interface. The end goal being $this->settings is never used in the workflow entity.
Remaining tasks
- Track progress of #2844594: Default workflow states and transitions.
- Write a patch.
User interface changes
None.
API changes
API changes to the way states and transitions are created and accessed.
Data model changes
Minimal data model changes, as the schema change has been done in #2844594: Default workflow states and transitions.
Comment | File | Size | Author |
---|---|---|---|
#63 | 2849827-workflow-setters-63.patch | 104.81 KB | Sam152 |
#63 | interdiff.txt | 838 bytes | Sam152 |
#57 | 2849827-workflow-setters-57.patch | 104.82 KB | Sam152 |
#57 | interdiff.txt | 1.42 KB | Sam152 |
#53 | 2849827-workflow-setters-53.patch | 104.68 KB | Sam152 |
Comments
Comment #2
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #3
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedInitial crack at this, with a BC layer left in, as suggested by @alexpott. While doing this, it felt like a few things fell into place and there were some simplifications that might point to this being a better schema. Namely:
I've implemented a test plugin which hardcodes 4 states and stores configuration for transitions. Simple example, but hopefully allows for more integration with broader concepts.
Comment #5
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #7
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #9
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #10
timmillwoodI know this is an experimental module, and we don't do upgrade paths, but I wonder if it might be a nice idea to in this case?
Otherwise I think this makes sense.
Comment #11
chiranjeeb2410 CreditAttribution: chiranjeeb2410 at Google Summer of Code commented@Sam152,
Patch applies cleanly, changes are good to go.
Updating to RTBC.
Comment #12
timmillwoodWe know the test applies cleanly, test bot told us that.
Back to needs review based on my previous comment.
Comment #13
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think it should go into the unofficial 8.3 => 8.4 upgrade patch + issue, once that's open (likely once 8.4 is released?) for a few reasons:
FWIW, I'll be writing and completing a full CM + workflows upgrade path for 8.3 to 8.4 at some stage, given I'm maintaining projects in production that have successfully used the 8.2 => 8.3 path.
Comment #14
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedReroll. I think it would be good to get this in before workflows hits stable, although it has been a while since it was written, so I might have a review of this myself before harassing anyone else to look at it :)
Comment #15
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think getting rid of this would be a big win for this patch. We don't use this pattern anywhere else, but defaultConfiguraiton is pretty ubiquitous.
I think these have been removed because previously you wouldn't have the transitions enforced by initializeWorkflow unless you actually called it. Now that the initialisation is being taken care of by the defaultConfiguration, you can't get into a state that doesn't have them merged in. Hence moving this test to the default states.
Probably worth adding some asserts that the workflow has states here.
This is possibly just "works as designed" and the @todo should be removed.
Comment #16
timmillwoodWhen manually testing I found this patch prevents transitions from being deleted. This also shows we don't have test coverage for deleting transitions.
Shall we just remove this?
Comment #17
timmillwoodUpdating to fix the issue of being unable to delete transitions, and providing better test coverage for that.
Comment #19
timmillwoodUnrelated fail, DrupalCI ran out of disk space.
Comment #20
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think the deep merge is what was enforcing the required states to exist, so I don't think this can be changed without something else to enforce them.
I think this implies the behavior was already in HEAD that create_new_draft and publish are always there, we just see them in the UI now. Maybe the correct behavior is for these to stick around?
Comment #21
timmillwoodThe issue doesn't exist in HEAD, it is currently possible to delete all transitions.
Access control is preventing states from being deleted (with and without the patch).
Comment #23
timmillwoodComment #24
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedRight, so initializeWorkflow is called only when saving a new workflow, not something that is called to enforce those states/transitions exist, so the shallow merge approach will probably work for our purposes, my mistake. The only possible issue is settings that are nested that aren't transitions or states would possibly not behave like the default configuration in other plugins. Worth thinking about this.
Comment #25
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think this is getting there.
I wonder if we could somehow only shallow merge on certain keys. At the very least I think this needs quite a detailed explanation of why we have to do this.
Other than that, I'm happy with it.
Comment #26
timmillwood@Sam152 - yes, this is a tricky one. The end goal here is to use the default configuration only for new workflows, but in
\Drupal\workflows\Plugin\WorkflowTypeBase::setConfiguration
we have no context about the workflow, only the workflow plugin. We might be able to go as far as$this->configuration = empty($configuration) ? $this->defaultConfiguration() : $configuration;
.Comment #27
timmillwoodHere's a simple option, as suggested in #26.
Comment #28
timmillwoodAfter looking at ImageEffectBase, FilterBase, VariantBase, ConditionPluginBase which @Sam152 pointed me to, this looks like a better solution.
Comment #29
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented+1 RTBC
Comment #30
timmillwoodWith @Sam152's "+1 RTBC", and my agreement, I think we're good to go.
Comment #32
timmillwoodUnrelated test fail, so back to RTBC
Comment #33
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI know this is experimental, but if we could not backport this to 8.3.x, that would be great. Will save a lot of headaches with the upgrade path, it would allow us to write upgrade paths for just the minor releases, not the patch releases.
Comment #34
timmillwoodI agree @Sam152, this should go into 8.4.x only!
Comment #36
timmillwoodFixing failing test.
Comment #37
plachThis is marked as MUST-HAVE in the roadmap.
Comment #38
plachI didn't review the whole patch but the interdiff makes sense, so moving back to RTBC.
Comment #39
plachIf this is the case I guess we need to create a follow-up and add it to the roadmap as a MUST-HAVE.
Comment #40
timmillwoodNote: I moved this issue up to must have because of it being a BC break and not having an upgrade path.
Adding follow up #2893778: Remove deprecated Workflows methods
Comment #42
timmillwood@plach maybe we update to "will remove before 8.5.0" not to make too much work for ourselves?
Comment #43
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI believe @alexpott originally suggested removing them in a follow up to keep the focus on the change in architecture vs all of the places these methods are used. I think the conversion will actually be quite trivial, if not a simple find/replace, but given this is experimental and we aren't really supporting BC in the first place, the comments themselves are somewhat misleading, implying there is any kind of BC to begin with? Maybe they should just be removed entirely?
Will have a look at the new fails.
Comment #44
timmillwoodHere's an update fixing the failing tests in #36.
Comment #45
plachI agree with @Sam152 let's just remove them in the follow-up, we wouldn't be able to do that after 8.4.0 is released. LEt's leave them as deprecated so if for any reason that doesn't get in at least we're covered.
Comment #46
plachI'd say we need to update the deprecation messages to mention D9 instead of 8.4.0. Then we will try to remove everything in the follow-up.
Comment #47
timmillwoodStating we'll officially fix the deprecated methods by D9, so that we can stabilize Workflows module.
However, I am currently working on them, so hope to have a patch soon.
Comment #48
timmillwoodoops, wrong file.
Comment #49
plachOk, back to RTBC.
The follow-up to remove deprecated methods is #2893778: Remove deprecated Workflows methods, if the patch is not ready in time to be merged here.
Comment #50
larowlanStarted reviewing this, found some phpcs issues - fixing those while I continue with review
Comment #51
larowlanLooking good - we need a change record here
Additional observations
nit: uppercase D
This is called a lot - is there merit in caching it in a protected property?
Perhaps we should add some comments here indicating why we're appending now instead of replacing?
Missing @throws on the interface docblock
Is there merit in making this a constant? (minor, personal preference)
We're instantiating an array of State objects and decorating them just to check if the state exists and then throwing those objects away. (::getStates calls ::getState which builds a State object and decorates it)
I think
isset($this->configuration['states'][$state_id])
is enough here.We've repeated this twice, perhaps merit in a utility protected sort method?
Could reuse ::hasState/::hasTransition
Is it worth static caching these in an object property, e.g.
$this->states
- multiple calls to ::getStates or ::getState rebuilds a new object each time?Should we be asserting that weight is numeric?
Feels like we should be doing the
$transition['to'] === $state_id
check first, and if it is, delete and callcontinue
and avoid the costly array_search for no reason. In addition, would allow us to get rid of the ugo elseif.If we go down the static cache of built State objects, would need to clean that up here too (see comment above)
Same question re static cache property - we rebuild these each time but they're thrown away.
Support my suggestion for using same approach in ::hasState (see earlier)
!$transition_id would suffice here, no need for
empty
- also '0' is a valid transition_id, so empty is technically the wrong choice (see https://3v4l.org/2d2PR)Strictly speaking, we should be using
=== NULL
, looking at the return from ::getTransitionIdFromStateToStateIn all other methods we avoid the else by negating the check and throwing the Exception early. I think we should do the same here, its more readable without the else.
Do we need to instantiate a Transition object here - we're only using the ID so ::getTransitionIdFromStateToState should be better than ::getTransitionFromStateToState
This feels like an unintended consequence of calling ::setTransitionFromStates, we're not changing the keys of $this->configuration['transitions'] here - I think this should go in ::addTransition instead, where we are adding a new transition and hence sorting would be required.
These need to reference a change record I think
We retain many methods and defer them to the workflow type, marked as deprecated - but we remove these two - is there a reason for that inconsistency?
If we're prescribing two possible values, really feels like they should be constants.
Comment #52
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for the review! I think I'll be able to have a look at these today.
Comment #53
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedGiven this is already quite big, I've addressed what seemed to be obvious fixes or improvements, opened up follow-ups where I think those improvements/changes should have their own testing/validation and put a few question marks where I didn't quite grok something.
Based on these two follow ups alone, I think it reaffirms why this change is worth it, we're essentially removing the need for two additional patterns we don't really see anywhere else (dx++):
#2896725: Entirely remove the concept of WorkflowType(Interface|Base)::initializeWorkflow()
#2896721: Remove the concept of decorated states/transitions from worfklow type plugins.
1. Fixed.
2. The plugin collection is cached in ::getPluginCollection and the plugin instance is cached in \Drupal\Component\Plugin\LazyPluginCollection::get. A new cache probably adds more complexity than reward.
3. If type plugins are now responsible for defining how states and transitions are stored, this breaks encapsulation anyway. Added a @todo to fix. I think we'll need submitStateConfigurationForm alongside buildStateConfigurationForm. Possibly a follow-up? #2896722: Leverage PluginWithFormsInterface to encapsulate @WorkflowType schema and clean up state/transition form methods.
4. Fixed.
5. Makes sense to me.
6. WorkflowTypeBase provides the foundation for a plugin that stores states and transitions in configuration, for plugins where this isn't the case, this is worse DX because it's more to override. @see \Drupal\workflow_type_test\Plugin\WorkflowType\PredefinedStatesWorkflowTestType.
7. Great clean up.
8. Fixed.
9. I think a reasonable follow-up will be to remove the methods for decorating states. The change in architecture this patch represents makes the concept irrelevant, because the type plugin is now the source of the states/transitions, so there is no need for the entity to create and decorate them. #2896721: Remove the concept of decorated states/transitions from worfklow type plugins.
10. Given this was basically just moved, I think it's a good idea, but scope creep.
11. Good catch, will have to look at this in some more detail. @todo
12. See comment above.
13. Lets follow this up in #2896721: Remove the concept of decorated states/transitions from worfklow type plugins. .
14. ?
15. Good catch.
16. Agree, fixed.
17. Good catch, fixed.
18. Agree, fixed.
19. We haven't created any change records thus far, do we need to for experimental modules? FWIW, we're only using this message to split the effort up into two issues, these will be removed right after this gets in.
20. I think these were just moved. Originally deleteState was on both the workflow entity and the type plugin, with the type plugin method existing to "React to the removal of a state". No longer required after this patch.
21. #2896724: Create constants for transition directions. for scope.
Comment #54
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #55
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedOpened another follow up #2896726: Expand the entity access model for workflow states and transitions..
Comment #56
larowlan14 was a comment in support of 6.
But your comment suggests we do it differently for states for a reason.
I would be inclined to do both the same way, the current hasState method needlessly instantiates a State object in my book. I think having to override is worth the performance improvement, when by and large most implementations will use configuration right?
Thanks for accommodating feedback
Comment #57
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFair enough, for the workflow type we are actually using, this will be faster. Fixed and overriden in the test type.
Comment #58
timmillwoodShould we open a follow up for #51.10 and #51.11
Other than that, looks good. Some awesome follow ups.
This is currently the biggest blocker to Workflows being stable. It's quite a new "must have" and only added because of the big schema changes. This could be a done in a BC way, with a full upgrade path in 8.5.x, but would be great to get such a big clean up issue in before stable.
Back to RTBC.
Comment #59
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #60
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI've filed #2897134: Enforce that weights are numeric when settings state/transition weights and #2897133: Address performance issues in \Drupal\workflows\Plugin\WorkflowTypeBase::deleteState. I think it makes sense to do these in the follow-ups given this issue didn't introduce either, only moved the code.
Comment #61
larowlanthis should be $object now right?
Comment #62
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented$objects is hinted as either an array of states or transition value objects:
+ * @param \Drupal\workflows\StateInterface[]|\Drupal\workflows\TransitionInterface[] $objects
Comment #63
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedEdit: right you are, missed the point of #61.
Comment #64
timmillwoodBack to RTBC
Comment #65
larowlanManually tested this once more on a fresh install.
Works well, we need a change record still though.
Comment #66
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedAdded change record.
Comment #67
larowlanCommitted 6772e2d and pushed to 8.4.x.
I think #2830740: Allow workflow types to lock certain changes to workflows once things are in use will need a re-roll now - will kick off a retest.
Published change record.
Un-postponed related issues.
Keep up the good work, getting closer to a beta now.