Overview
#3491978: Implement saving block settings forms is fixed, which means block settings can now be stored (and since #3500997: Move SDC-specific validation in ValidComponentTreeConstraintValidator and ComponentTreeMeetsRequirementsConstraintValidator into the SDC source plugin are guaranteed to be valid).
Maintainers of block plugins can make changes to their config schema (to add/change/remove settings).
How does XB allow those maintainers to provide an update path that also updates all XB-stored component trees?
See search_post_update_block_with_empty_page_id(), tested by SearchBlockPageIdUpdatePathTest and added in #3379725: Make Block config entities fully validatable for an example — which ensures the search block's page_id setting is valid.
Proposed resolution
Impossible original plan because of a core limitation: #3521221: Block plugins need to be able to update their settings in multiple different storage implementations.
We need to write an update path + update path test that does the exact same thing for a search block stored in an XB component tree.Start with an XB component tree in an XB field on a content entity.Then ensure it also updates an XB component tree in e.g. thePageTemplateconfig entity.
This likely can take inspiration from #3450957: Prevent modules from being uninstalled if they provide field types used in an Experience Builder field.
New plan: #15; which removes the goal of an actually working automatic update path (impossible due to #3521221), and replaces it with a manual one — just to prove viability.
Ideally, this would test not only a net-new key-value pair (which is an explicit input schema change), but also a changed shape in an existing key-value pair — for example from string to integer, or from one list of allowed values to another (with no intersection).
Note: this is not a problem that is unique to block plugins — it applies to all ComponentSource implementations with >=1 component discovered by them whose ComponentSourceInterface::requiresExplicitInput() returns TRUE.
For SDCs and code components, similar challenges exist (see #3509115: [META] Plan to allow editing props and slots for in-use code components), but there no update paths have historically been provided, whereas for block plugins they have been.
User interface changes
None.
Issue fork experience_builder-3501708
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
wim leersComment #3
wim leersComment #4
wim leersComment #5
nagwani commentedComment #6
isholgueras commentedCan we use #3501290: Introduce unit test coverage for both ComponentSource plugins (Block + SDC) to use
xb_test_blockas blocks to update?Comment #7
wim leersComment #8
wim leersPlan articulated at #3520484: [META] Production-ready ComponentSource plugins.
Comment #9
longwaveAre we thinking here that we need block implementations to provide separate code paths to update XB blocks? If so, what happens if a contrib author decides not to do that? Or, who takes responsibility for core block updates, if core does not depend on XB?
On the other hand, I am not sure if this will work, but I wonder if we can do this without contrib maintainers needing to do anything at all! Block updates are generally written like this:
Can we decorate ConfigEntityUpdater so as well as updating config entities, we additionally reconstruct config entities for blocks in XB content, and allow the closure to make changes to them in the same way? The closure is not responsible for saving the config entity - so because these won't be real config entities, we never need to directly save them; we can just extract the settings (or whatever else might have changed?) and put them back into the XB storage.
Comment #10
wim leersWill discuss #8 + #9 tomorrow with @longwave :)
Comment #11
catchI think that this is an existing core bug, because we should already be doing the same thing for layout builder and navigation modules, but we don't.
Fixing it requires adding a new API to core, don't see another way, I opened #3521221: Block plugins need to be able to update their settings in multiple different storage implementations which doesn't have a nice API but I think probably does have the places that API would need to be implemented and roughly what it would need to do.
I'm not postponing this issue on that one yet, but I think it probably should be.
The sentence that got me to that point was this one from the parent issue:
This is the missing piece that the new BlockPluginUpdater API would address.
Comment #12
larowlanGiven block updates would be writing config, can we instead listen to
\Drupal\Core\Config\ConfigEvents::SAVEand detect if it occurs during an update/post update (assuming that's possible) and if so propagate those changes to component trees we can identify as referencing blocks via #3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operationComment #13
catchI don't think it's possible to reliably detect whether the save happens during an update/post-update except perhaps via a backtrace and function name guessing (e.g. does post_update appear in the stack trace).
Assuming that could be detected though, it would need to compare the before and after of the block entity, determine what the change is, and then apply that to the update - it wouldn't be possible to run the same code that the update itself does, only to reverse-engineer the change from the config entity original.
Some block post updates won't result in any config saves at all, because there's no affected blocks on the site, but the plugins being targeted could be used in XB templates - then there's nothing to listen to. The search block would be an example where it might be in an XB template but not used in a block region.
Additionally, this approach wouldn't be able to take into account xb templates that are shipped as config with modules or recipes, which equally need to apply the same changes so that they're correct after import/recipe apply.
Comment #14
wim leersThanks to #3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation + #3521202: Store XB field type's "deps_*" columns in separate table to allow efficient querying, it's now trivial to find all the content entity revisions (and config entities) in which a certain block plugin is used in an XB component tree.
But what's still missing is fundamental core support for generically updating the inputs expected by plugins that may be used in multiple contexts, and not just e.g.
Blockconfig entities. The core issue for that is #3521221: Block plugins need to be able to update their settings in multiple different storage implementations.Thanks, @catch, for opening that, and stating it as starkly as you did: — I don't either. 😬
Self-assigned in #10, forgot to link here to the write-up I posted at #3520484-22: [META] Production-ready ComponentSource plugins as promised. Unassigning.
Comment #15
wim leersOnce #3468272: Store the ComponentTreeStructure field property one row per component instance is in, I think we should be able to create a working PoC here that does the following in a test:
input_schema_change_pocblock plugin in thexb_test_blockmodule which has e.g.['foo' => 'bar']as its default configuration, and has this as its config schema:xb_test_block_simulate_input_schema_changemodule that:['foo' => 'bar', 'change' => 'is scary'], plus changes its render logic in::build()so we can easily assert the differenceRenderSafeComponentContainer\Drupal\experience_builder\Audit\ComponentAuditto find all instances, in both config and contentblock.xb_test_block_simulate_input_schema_changein all component trees across both content and config👆 That would allow us to be as confident as we can that we'll be able to do #3524751: [later phase] Component Source plugins: generalized support for schema changes of explicit inputs in the refactored server-side data model 👍
Comment #17
wim leersJust did a video call with @isholgueras to unblock him on next steps and clarify the unclear bits in my outline at #15 (🙈 Sorry!).
Updated issue title + summary to convey the intended scope.
Comment #18
wim leersExplain relation to SDC and code components.
Comment #19
wim leersFix HTML 🙈
Comment #20
isholgueras commentedComment #21
isholgueras commentedComment #22
wim leersReviewed and … nice progress here! 👍😄
Steps 4 and 5 in the plan I articulated in #15 are not yet done.
Step 4 is not yet done due to a bug in the test coverage, once that's fixed, that'll force #4 😇
Comment #23
wim leersComment #24
wim leersFYI: #3523841: Versioned Component config entities (SDC, JS: prop_field_definitions, block: default_setting, all: slots for fallback) + component instances refer to versions ⇒ less data to store per XB field row just landed, and ideally the test coverage for #15.5 would live in
ComponentInputsEvolutionTest. That already containstestStorablePropShapeChanges(), this MR would addtestInputSchemaChanges(). 🙏Comment #25
larowlanLate to the party but #15 sounds good to me.
Comment #26
larowlanComment #27
isholgueras commentedComment #28
wim leers@larowlan in #26: thanks, much appreciated! 🙏
I kicked off a deep review and pushed a few small fixes/improvements which revealed test weaknesses, @f.mazeikis will continue 👍
P.S.: The 3 new test methods added to
BlockComponentTestshould be moved as I described in #24. 🙏 This observation underlines that.Comment #30
f.mazeikis commentedBetween @isholgueras addressing some of your feedback and me moving tests out of
BlockComponentTestintoComponentInputsEvolutionTestI think this is ready for another review.I have also taken liberty to rename test methods and expand their doc blocks, in attempts to make this PoC easier to read.
Comment #31
wim leersComment #32
wim leersPer @larowlan's review remarks 1 and 2, let's land #3526127: Ensure deterministic config export order of config-defined component trees first.
Comment #33
wim leers#3526127: Ensure deterministic config export order of config-defined component trees is in :)
Comment #34
wim leersActually … I'm pretty sure that @larowlan meant to link to #3528362: Deterministic Component version hash should depend not only on source-specific settings, but also slots + explicit input schema instead of #3526127: Ensure deterministic config export order of config-defined component trees 😅
Comment #35
wim leersI found some pretty serious flaws in the test coverage: 1, 2, 3, 4 and most importantly: this was indeed asserting various edge cases/expectations wrt updates to components in the
blockComponentSource, but was not yet testing the very purpose of this issue as described in #15.5:proving that a future automated update path will be possible once #3521221 in core adds the necessary infra.To be fair: this issue/MR is very abstract and is about ensuring something will be possible, which makes it much harder to write tests 😬. But it's really important that the tests introduced here A) are super clear, B) inspire confidence that we'll be able to support this later without BC breaks to the data model/storage (see #3520449: [META] Production-ready data storage).
Will push this over the finish line. Already addressed ~half of my concerns, other half to follow.
Comment #36
wim leersSee the remaining open MR threads — they provide pointers and context for the most crucial test coverage changes/enhancements.
The plan I outlined in #15 is now fully implemented.
(Note: some of the test clarity improvements I added today were impossible until #3523841: Versioned Component config entities (SDC, JS: prop_field_definitions, block: default_setting, all: slots for fallback) + component instances refer to versions ⇒ less data to store per XB field row last week!)
Next steps are:
ComponentAuditservice in #3524751: [later phase] Component Source plugins: generalized support for schema changes of explicit inputsComponentAuditservice in #3524751: [later phase] Component Source plugins: generalized support for schema changes of explicit inputs and/or #3528499: Revisit storage of dependencies in separate table now we have separate deltas per component + remove plugin dependencies + make component version usages auditableComment #37
wim leersComment #39
wim leersThis apparently introduced 2 test failures on all DBs except SQLite: https://git.drupalcode.org/project/experience_builder/-/pipelines/519184 —
JsComponentTest::testFallback()andSingleDirectoryComponentTest::testFallback().This makes it pass again:
… but that's a necessary change. I bet @larowlan will figure this out in no time 🤓
Comment #41
larowlanNew MR https://git.drupalcode.org/project/experience_builder/-/merge_requests/1125 has the fix.
The SQL fails were 'data too long' which (see the screenshot above) was because when component was updated it was forcing the component version to match.
The intent of the change added in this issue was to permit setting a new component version and having
::getComponentload the correct new version.I've rewritten it to work as intended and it fixes the tests. Luckily we put a 16 char limit on that column else we wouldn't have caught this.
Comment #42
wim leersClearly I misunderstood how “pairs” worked 😅
Thanks!
Comment #44
wim leers