Overview
Follow-up to #3455728: FieldType: Support storing component *trees* instead of *lists*. In that issue we made it so \Drupal\Core\TypedData\TypedData::__construct stores tree values(not an actual in json but a value we can construct into a tree).
But just added a simple exception for the validation.
We need move that to a constraint.
Proposed resolution
From @wim leers comment
We need to add a validation constraint for `ComponentTreeStructure`. Note that #3456024: Lift most logic out of ComponentTreeItem::preSave() and into a new validation constraint is _not_ overlapping with this: that is about validating the `ComponentPropsValues` _for a given `ComponentTreeStructure`. But long before we get to that point, we need to ensure that at least the tree structure itself is valid (independent from the _values for the props of the components in that tree_, which is what #3456024: Lift most logic out of ComponentTreeItem::preSave() and into a new validation constraint is about).
That validation constraint should validate that:
- the root UUID is present
- any top-level UUID appears as a UUID in a _parent branch/tree_ (except its own branch), if the component for that UUID is a component that has >=1 slots
- slots of every component: the stored slot names (
firstColumnandsecondColumnin the example at #3455728: FieldType: Support storing component *trees* instead of *lists*) MUST be actually existing slot names for this particular component instance.
User interface changes
Issue fork experience_builder-3460856
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
tedbowPostponing on #3455728: FieldType: Support storing component *trees* instead of *lists*
Comment #4
tedbowComment #5
wim leersI see this now, after I already wrote #3455728-9: FieldType: Support storing component *trees* instead of *lists* …
Actually … why don't we merge this with #3456024: Lift most logic out of ComponentTreeItem::preSave() and into a new validation constraint — i.e. why don't we expand the scope of that? It's exactly the same problem space!
Comment #6
tedbowComment #7
wim leersPer #3456024-15: Lift most logic out of ComponentTreeItem::preSave() and into a new validation constraint, keeping this issue around, not marking it as a duplicate. This is blocked on #3456024.
This is the next most important issue, because it is in the critical path of #3446722: Introduce an example set of representative SDC components; transition from "component list" to "component tree", which is itself getting in the critical path of next steps on the UI side.
Pre-emptively assigning to @tedbow — now that #3456024 is nearly done, this should go a lot faster. 🤞
Comment #8
wim leersBlocker is in.
Comment #9
tedbowComment #11
tedbowComment #12
wim leersThis is going in the right direction 👍
Review
ComponentTreeStructureTestlooks good but is not yet testing all edge cases. I see at least two missing important edge cases:👆 these are edge cases that I described succinctly in the issue summary as
\Drupal\Tests\experience_builder\Kernel\Config\DefaultFieldValueTestthat triggers a failure for this new constraint for a config-defined component tree?json_encode([ … stuff … ])instead.Comment #13
wim leersI just realized an important validation piece is missing if this is to unblock #3446722: Introduce an example set of representative SDC components; transition from "component list" to "component tree".
Comment #14
tedbowre #12
and
I don't think this first case is actually what is described in the summary but rather the inverse(unless we are still not talking about the same of constructing the tree from the storage)
UUIDs under the root should not need to have top-level keys, some will but components without slots should not need any other information in the tree. I confirmed with Jesse Baker that Components are not required to have slots.
There is a typo here I think but again I think a child of the first level should not be required to exist as top level key because the child of the first level might not have a slot that needs representing in the tree.
So I think this tree would valid but it does NOT pass your suggested validation
Sorry for the weird naming basically I thought "component-with-no-slots" will be dead-end in the tree and that is by design.
I have updated the test cases to use either
"component" => "component-no-slots"or"component" => "component-slots"to make this clearer.I think that is what this existing case is catching
but I have expanded the tree for the case to this
Yes, will do this, but not done yet.
Should we also add the test cases to
ComponentTreeItemTest::testInvalidField()to make it sure it triggers for field instances? Right now they use the same helper function,ComponentTreeTestTrait::getComponentTreeTestCases()to provide test cases so we test the same cases in both placesAgreed, fixed
Leaving assigned to myself to do the remaining items but I need confirmation from @Wim Leers that I am right about 1)
Comment #15
tedbow@Wim Leers I am working on this part now
I haven't actually dealt with this part of the module at all. Any tips here. How do I load a component from an id? Is this always config component as defined by
\Drupal\experience_builder\Entity\Component? there is no class comment so hard to tell. Or could this any sdc component. I have loaded a Component config entity but also there I can't see slot info there.Comment #16
tedbow@Wim Leers assigning back to you for questions on #14. If you have pointers for #15 that would be great(otherwise when I get back this issue I will keep looking)
Comment #17
wim leers#14:
Indeed 👍
Yes 👍 (And → I think it's excellent naming 😄)
You're right — my bad! 🙈
\Drupal\experience_builder\Plugin\Validation\Constraint\ValidComponentTreeConstraintValidator::validate()already needs to do that anyway — so either way, the caches would be warm.Overall, I'd rather err on the side of more detailed validation for now, to allow us to not have to ever think about this again after this issue — because this issue will immediately catch/flag any mistake we'll make on our way to the full XB functionality scope.
#15: you can load a component from an ID using
\Drupal\experience_builder\Entity\Component::loadByComponentMachineName().Comment #18
tedbow@Wim Leers sorry for further confusion
but originally in #14 I typed
But meant this.
You responded to this in #17 so wanted to called that out.
I edited #14
Comment #19
wim leersuuid-unknown-top-levelis danging/leftover and is the expected validation error message … That's why I replied affirmatively. I don't see how that tree would be valid?Comment #20
tedbowre #19
The correction in #18 I was referring to was actually for #14.1
not #14.2
Comment #21
wim leersI agree that the example you provided in #14.1 MUST be considered valid.
In fact, if
UUID-IN-ROOT-NOT-TOP-LEVELdid appear as a top-level key, then THAT would be a violation.I see I didn't respond to this bit in #15:
\Drupal\Core\Plugin\Component::$machineName) for a Component config entity, by using\Drupal\experience_builder\Entity\Component::convertIdToMachineName().Componentplugin instance usingplugin.manager.sdc\Drupal\Core\Plugin\Component::$metadatato determine what the slots are.… adding a helper method to the config entity would be fine, of course.
Comment #22
tedbowComment #23
wim leersThis is making a pretty drastic change that is not captured in the issue summary: https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... — in that comment I explain why we should revert that and leave such a change for #3454519: [META] Support component types other than SDC, block, and code components to make.
I also found 2 critical gaps in the test coverage:
… and a number of additional questions.
Comment #24
tedbow@Wim Leers I changed this to use symfony/validator constraints. I think this is better.
I still need to add some test cases for missing keys and type errors but this logic should already be covered by the constraints.
So I have still have work on it. If you want to just wait until I am done, I can just continue working on it. But if you want to look at the approach feel free
Comment #25
wim leersWow, that looks much better! 👏
I'd even use the word impressive — it's nice to see that when we use Symfony's Validation component "as intended", that it is so much clearer than if there's Drupal's layer of abstraction (either config schema or Entity/Field/Typed Data) in between 🤯
Comment #26
wim leersI know this issue is not yet marked as (especially because you wrote at https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... that you need more test cases before addressing that), but given its importance, pre-emptively reviewed to make this go faster 😊
I think this is now in its final stretch — the Symfony-based validation logic makes this much easier to understand! 👍
Comment #27
tedbow@Wim Leers see MR comments
Comment #28
wim leersThis looked great, but given its foundational importance, I subjected it to a lot of scrutiny. Some bits I found hard to understand, so I worked to make that easier for the next person. And since there will be many next persons … I spent the entire day on this 😳
Summary of changes I made:
BasicRecursiveValidatorFactoryto ensure validation error messages useTranslatableMarkupjust like other validators — see https://www.drupal.org/node/339620390% of what @tedbow wrote is still there. I just smoothened out the rough edges.
So glad to have this done 😊
Comment #30
wim leers🚢
Unblocked: