Overview
All content templates, by definition, contain at least one dynamic prop source (or it wouldn't be much of a template). Those dynamic prop sources could refer to bundle fields of entities (e.g. field.field.node.blog.tags).
What happens if one (or more) of those fields gets deleted?
Well, at the moment, the field is calculated as one of the config dependencies of the content template (that was done comprehensively in #3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation). But it's a hard dependency, so if the field is deleted, the content template would be too! That's obviously very bad.
Proposed resolution
When a field is deleted, the content template should change itself, clearing out any inputs that are referring to that field with a dynamic prop expression. This should make XB to fall back to the default (example) values for those inputs. The content template should not be deleted.
This can be done by implementing ConfigEntityInterface::onDependencyRemoval() for content templates. In there, any input which relies on the deleted field should be replaced with a static prop source (i.e., an example value if available).
User interface changes
Attempting to delete a bundle field should result in a confirmation dialog that lists the referencing ContentTemplate config entity as using it, and informing you that the template will be changed.
Issue fork experience_builder-3518336
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 leersThis is inaccurate.
ComponentTreeItemdoesn't allowDynamicPropSources. That's been the case since #3481720: Tighten validation: only allow StaticPropSource in XB fields + PageTemplate, DynamicPropSource in ContentTypeTemplate.Only
ContentTemplateconfig entities' (being introduced in #3517741, which spawned this issue to keep scope tight there) component trees will be allowed to useDynamicPropSources.Added the proposed resolution. Can you confirm it makes sense to you? 🙏
If it does, please update the in the issue summary — that's why I tagged this for needing an IS update.
Comment #5
danielvezaIm pretty interested in following the ContentTemplate work, so I figured I'd make a start on this one based on the IS and the discussion from #2. I've pushed up a POC of the existing work I've done so far, which now adds config dependencies for fields to the content template entities. This has test coverage, but still additional work to be done around code cleanup and removing the legacy code from `ComponentTreeItemTest`
Comment #6
wim leersWoohoo, thanks so much! 😊
Comment #7
wim leersComment #8
wim leersPer #3518248-11: [PP-1] Content templates, part 4 (boss battle): create a UI for editing templates, this should block that.
Comment #9
phenaproximaI did some work on this to move the dependency resolution into
ComponentInputs. I also made sure that it checks for base field overrides, which are another kind of config dependency (and we do need to test for this).Comment #10
phenaproximaI have a question that either Wim or Lauri need to answer, brought up by this issue.
The way the MR currently works, a content template that contains a dynamic prop expression targeting a field -- base field override or normal bundle field -- will cause the template to be dependent on that field.
That makes sense from a data integrity standpoint, but are we not setting up a situation where, if a site builder deletes a field, all templates that mention that field are deleted too? That seems extremely bad. I understand that core will show a confirmation page where it lists the related config that will also be deleted, but do we truly expect most site builders to read through that page, understand it, and act accordingly?
What should we do here? We probably need
ContentTemplateto implementonDependencyRemoval()to handle this case in some way, or XB to be able to show a graceful fallback (assuming it doesn't already) if a prop expression is referring to a field that no longer exists. Either way, this needs product and tech lead input, so assigning this to Wim before we do any more work on it.Comment #11
lauriiiWe certainly should not delete the whole content template as a result of deleting a field. 😅 I think we should just remove the use of the dynamic prop from the content template. In case the deleted field happens to be a required, we replace the dynamic prop expression with example value.
Comment #12
phenaproximaCool. This sounds like it's more of a frontend issue, then, and not really a data integrity problem (and thus, I'd imagine, not a stable blocker).
Comment #13
wim leersHow/why do you conclude that? 🤔 😅
It's not the front end that is deleting a
FieldConfig; that's a pure server-side concern. It might also happen through a configuration sync.This is the
ContentTemplateequivalent of #3484682-12: Handle update and delete of Block `Component`s, plus react to dependency removal: we need to implementContentTemplate::onDependencyRemoval().Comment #14
wim leersThis is at minimum soft-blocked on #3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation, but not yet sure whether I should hard-block it on that. Your help on that issue would surely be appreciated, @phenaproxima 🙏
Comment #15
wim leersComment #16
wim leersSo: @lauriii and I +1'd @phenaproxima's proposal in #10:
… and I missed that while writing #13 — but you can see that there's other cases where we still need to do that, @phenaproxima — it'd be nice for you to lead the way here :)
Comment #17
wim leersSee #3457504-33: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation — as of that moment, that MR implements a superset of
ComponentInputs::getDependencies()that this MR was adding.That means that:
::onDependencyRemoval()part 🥳@phenaproxima: can you please help finish that issue? 🙏
Comment #18
wim leersRelated, from ~10 months ago, where we predicted exactly this issue: #3452848: [PP-1] Test coverage to prove configurable fields cannot be deleted from content entity types if they are used in XB Content Type Template.
Comment #19
wim leers#3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation is in!
The current MR is AFAICT obsolete; everything in it has already been implemented (in a more comprehensive manner) in #3457504.
That means that the remaining scope here should be implementing (and testing)
::onDependencyRemoval().Comment #20
phenaproximaTook a crack at this - still needs tests and such, but does it look basically like what you're hoping for? I'm not sure what to do if a prop expression in a content template is referring to a removed field, but I'm hoping that just unsetting the input will allow it to fall back to the example value.
Comment #21
phenaproximaActually, never mind - the issue summary definitely says what a reasonable test would look like.
Comment #22
phenaproximaRe-titled and updated the issue summary to reflect the current realities.
Comment #23
wim leersIndeed!
Yes, that's what @lauriii proposed in #11 👍
In my mind, we would just delete the component instance. But … I think what you propose here (and I guess makes more sense indeed! 😄
So:
ContentTemplate(forarticlenodes) with aherocomponent instance whosemarqueeprop is populated byi.e. it fetches the
valuefield property of thefield_funny_messagefield on thearticlenode type.This was a
DynamicPropSource.field_funny_message; and it's not reasonable for aContentTemplateto block that, but theContentTemplateshould also not be deleted.ContentTemplate::onDependencyRemoval(), checks iterates over all component instance inputs and finds the one that actually depends on this particular field (i.e. on thefield.field.node.article.field_funny_messageconfig). It replaces theDynamicPropSourcewith aStaticPropSource.Rough outline of algorithm:
ComponentSourceInterface::requiresExplicitInput(). If not:[](the empty array)Component::getSource() instanceof GeneratedFieldExplicitInputUxComponentSourceBase— only these sources can be populated by fields.👆 This should return something like
(You will need to refactor
GeneratedFieldExplicitInputUxComponentSourceBaseto make certain methods available toContentTemplate. For alternative implementation inspiration, seeGeneratedFieldExplicitInputUxComponentSourceBase::buildConfigurationForm()).ComponentSourceInterface::validateComponentInput()on the end result\Drupal\experience_builder\Plugin\Validation\Constraint\ValidComponentTreeConstraintValidator::validate()IOW: kinda like
\Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval():)Comment #24
phenaproximaWrote an initial (functional) test, but I will still need to update the code based on Wim's outline in #23.
Comment #25
phenaproximaComment #26
phenaproximaProbably ready for an initial review.
Comment #27
phenaproximaComment #29
wim leersComment #30
phenaproximaOK, I did some refactoring here to both bring things more in line with what Lee and Wim are asking for, and make it so that only the affected inputs are the ones that get replaced or cleared out (that bit still needs test coverage).
Comment #31
wim leersVery close!
And very nice to see the new
ComponentSourceInterface::getDefaultExplicitInput()!This is now only missing test coverage IMHO: https://git.drupalcode.org/project/experience_builder/-/merge_requests/9...
Comment #32
wim leers@phenaproxima, also, correct me if I'm wrong, but AFAICT #3452848: [PP-1] Test coverage to prove configurable fields cannot be deleted from content entity types if they are used in XB Content Type Template is made obsolete by this issue, isn't it? 😄🥳
Comment #33
phenaproximaYou probably have it better loaded into your brain than I do, but from the look of that issue summary, I would say we can close that as outdated.
Comment #34
phenaproximaSending back to NR to look at the added test coverage.
Comment #35
wim leersBasically only a naming remark left — other than that, this is is IMHO good to go!
Please assign this to @longwave for final review after you've addressed the naming nit.
@longwave, I think the new
::getDefaultExplicitInput()should be used in multiple places inGeneratedFieldExplicitInputUxComponentSourceBase— curious if you agree. That'd allow us to be more confident about it IMHO.Comment #36
phenaproximaAssigning to @longwave for final review.
Comment #37
phenaproximaComment #39
longwaveI haven't reviewed the rest of the code in detail, but I think Wim is confident with it; back to @phenaproxima for a review on the part I just changed then I think you can RTBC if you are happy with that?
Comment #40
longwavePushed a further refactor that removes another ~10 lines of code because it looks like we can assume
default_valueis always set. If this fails tests then let's revert and move that to a followup.Comment #41
phenaproximaNo objections to anything you did, @longwave.
Comment #42
phenaproximaSince this has been cleared by @longwave, assigning back to Wim for final review.
Comment #43
longwaveThanks Wim - was about to dig into this this morning but you've beaten me to it. I think we could handle the "new prop" case in a followup somewhere and finish the refactor, but let's not hold this up any more.
Comment #44
wim leersWas failing tests. Wanted to see this through, so debugged, and found that reverting a single line was sufficient to make it pass. Added a clarifying comment.
Glad to have @longwave's +1 for the new
GeneratedFieldExplicitInputUxComponentSourceBase::getDefaultExplicitInput().Agreed with @longwave's suggestion to improve it + adopt it more widely in a follow-up. Tagging for that. (That will be squarely in the issue queue component, rather than issue's . 👍)
@phenaproxima's additions of
ComponentInputs::getPropSourcesWithDependency()andComponentTreeItem:: updatePropSourcesOnDependencyRemoval()look fantastic (plus, the test coverage of course). The latter new method will undoubtedly come in handy when updating thePatternandPageRegionconfig entities. Created #3524345: Avoid deleting `PageRegion`s & `Pattern`s when uninstalling field type-providing module: implement `::onDependencyRemoval()` , replace with default StaticPropSource for that.Comment #45
wim leersCross-posted with @longwave in #43 — obviously we agree 😁
Updating title, because thanks to
::testRemoveFieldTypeProviderModule(), @phenaproxima actually solved more while at it 🥳Comment #47
wim leersComment #48
wim leersThe part was actually solved in #3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation.