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.

Command icon 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

phenaproxima created an issue. See original summary.

wim leers’s picture

Title: Component trees with dynamic prop sources that introspect field values don't add the relevant fields to their dependencies » Content Template's component tree can be populated by DynamicPropSources; should result in dependencies on relevant FieldConfig entities
Component: Code » Config management
Assigned: Unassigned » phenaproxima
Category: Bug report » Task
Priority: Normal » Critical
Issue summary: View changes
Issue tags: +stable blocker, +data integrity, +missing config dependencies, +Needs issue summary update
Related issues: +#3481720: Tighten validation: only allow StaticPropSource in XB fields + PageTemplate, DynamicPropSource in ContentTypeTemplate

This is inaccurate.

ComponentTreeItem doesn't allow DynamicPropSources. That's been the case since #3481720: Tighten validation: only allow StaticPropSource in XB fields + PageTemplate, DynamicPropSource in ContentTypeTemplate.

Only ContentTemplate config entities' (being introduced in #3517741, which spawned this issue to keep scope tight there) component trees will be allowed to use DynamicPropSources.


Added the proposed resolution. Can you confirm it makes sense to you? 🙏

If it does, please update the Overview in the issue summary — that's why I tagged this for needing an IS update.

danielveza made their first commit to this issue’s fork.

danielveza’s picture

Im 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`

wim leers’s picture

Woohoo, thanks so much! 😊

wim leers’s picture

Status: Active » Needs review
wim leers’s picture

phenaproxima’s picture

Issue tags: +Needs tests

I 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).

phenaproxima’s picture

Assigned: phenaproxima » wim leers

I 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 ContentTemplate to implement onDependencyRemoval() 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.

lauriii’s picture

We 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.

phenaproxima’s picture

Assigned: wim leers » phenaproxima

Cool. 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).

wim leers’s picture

This sounds like it's more of a frontend issue, then,

How/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 ContentTemplate equivalent of #3484682-12: Handle update and delete of Block `Component`s, plus react to dependency removal: we need to implement ContentTemplate::onDependencyRemoval().

wim leers’s picture

Title: Content Template's component tree can be populated by DynamicPropSources; should result in dependencies on relevant FieldConfig entities » [PP-1] Content Template's component tree can be populated by DynamicPropSources; should result in dependencies on relevant FieldConfig entities

This 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 🙏

wim leers’s picture

wim leers’s picture

Status: Needs review » Needs work

So: @lauriii and I +1'd @phenaproxima's proposal in #10:

We probably need ContentTemplate to implement onDependencyRemoval()

… 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 :)

wim leers’s picture

Title: [PP-1] Content Template's component tree can be populated by DynamicPropSources; should result in dependencies on relevant FieldConfig entities » [PP-1] Implement ContentTemplate::onDependencyRemoval()

See #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:

  1. This is now hard-blocked on #3457504: XB field type: calculate all dependencies, store them, surface in new Component "Audit" operation — because that other MR clearly is the more precise, complete, reliable solution.
  2. This issue can now be rescoped to handle the ::onDependencyRemoval() part 🥳

@phenaproxima: can you please help finish that issue? 🙏

wim leers’s picture

Title: [PP-1] Implement ContentTemplate::onDependencyRemoval() » Implement ContentTemplate::onDependencyRemoval()

#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().

phenaproxima’s picture

Status: Needs work » Needs review

Took 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.

phenaproxima’s picture

Status: Needs review » Needs work

Actually, never mind - the issue summary definitely says what a reasonable test would look like.

phenaproxima’s picture

Title: Implement ContentTemplate::onDependencyRemoval() » When a field used by a content template is deleted, any inputs in that template which refer to that field should be cleared
Issue summary: View changes
Issue tags: -Needs issue summary update

Re-titled and updated the issue summary to reflect the current realities.

wim leers’s picture

Status: Needs work » Active

The content template should not be deleted.

Indeed!

This should make XB to fall back to the default (example) values for those inputs.

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:

  1. I have a ContentTemplate (for article nodes) with a hero component instance whose marquee prop is populated by
    {
      "marquee": {
        "sourceType":"dynamic",
        "expression":"ℹ︎␜entity:node:article␝field_funny_message␞␟value"
      }
    }
    

    i.e. it fetches the value field property of the field_funny_message field on the article node type.

    This was a DynamicPropSource.

  2. The site builder wants to delete field_funny_message; and it's not reasonable for a ContentTemplate to block that, but the ContentTemplate should also not be deleted.
  3. ContentTemplate::onDependencyRemoval(), checks iterates over all component instance inputs and finds the one that actually depends on this particular field (i.e. on the field.field.node.article.field_funny_message config). It replaces the DynamicPropSource with a StaticPropSource.

    Rough outline of algorithm:

    1. Check if this component requires explicit input ComponentSourceInterface::requiresExplicitInput(). If not: [] (the empty array)
    2. Sanity check: assert that Component::getSource() instanceof GeneratedFieldExplicitInputUxComponentSourceBase — only these sources can be populated by fields.
    3. (Untested PoC.) Populate all props with examples (which means: all required ones, plus the optional ones that do have an example).
      $component = Component::load(…);
      $source = $component->getSource();
      assert($source instanceof GeneratedFieldExplicitInputUxComponentSourceBase);
      if (!$component->requiresExplicitInput()) {
        return [];
      }
      
      // @see `type: experience_builder.generated_field_explicit_input_ux`
      $props_with_examples = array_keys(array_filter(
        $source->getSettings()['prop_field_definitions'],
        fn (array $def) => $def['default_value'] !== NULL
      ));
      return array_map(
        fn (string $prop_name) => $source->getDefaultStaticPropSource()->toArray()
        $props_with_examples
      );
      

      👆 This should return something like

      {
        "marquee": {
          "sourceType":"static:field_item:string",
          "value": "Hello, world!",
          "expression":"ℹ︎string␟value"
        }
      }
      

      (You will need to refactor GeneratedFieldExplicitInputUxComponentSourceBase to make certain methods available to ContentTemplate. For alternative implementation inspiration, see GeneratedFieldExplicitInputUxComponentSourceBase::buildConfigurationForm()).

    4. ComponentSourceInterface::validateComponentInput() on the end result
    5. Prior to saving, verify the end result by using \Drupal\experience_builder\Plugin\Validation\Constraint\ValidComponentTreeConstraintValidator::validate()

    IOW: kinda like \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval() :)

phenaproxima’s picture

Issue tags: -Needs tests

Wrote an initial (functional) test, but I will still need to update the code based on Wim's outline in #23.

phenaproxima’s picture

Title: When a field used by a content template is deleted, any inputs in that template which refer to that field should be cleared » When a field used by a content template is deleted, any inputs in that template which refer to that field should be replaced with static default values
phenaproxima’s picture

Assigned: phenaproxima » Unassigned
Status: Active » Needs review

Probably ready for an initial review.

phenaproxima’s picture

Issue summary: View changes

wim leers’s picture

Status: Needs review » Needs work
phenaproxima’s picture

Status: Needs work » Needs review

OK, 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).

wim leers’s picture

Assigned: Unassigned » phenaproxima
Status: Needs review » Needs work
Issue tags: +Needs tests

Very 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...

wim leers’s picture

@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? 😄🥳

phenaproxima’s picture

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?

You 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.

phenaproxima’s picture

Status: Needs work » Needs review

Sending back to NR to look at the added test coverage.

wim leers’s picture

Status: Needs review » Needs work

Basically 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 in GeneratedFieldExplicitInputUxComponentSourceBase — curious if you agree. That'd allow us to be more confident about it IMHO.

phenaproxima’s picture

Assigned: phenaproxima » longwave
Issue tags: -Needs tests

Assigning to @longwave for final review.

phenaproxima’s picture

Status: Needs work » Needs review

longwave made their first commit to this issue’s fork.

longwave’s picture

Assigned: longwave » phenaproxima

I 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?

longwave’s picture

Pushed a further refactor that removes another ~10 lines of code because it looks like we can assume default_value is always set. If this fails tests then let's revert and move that to a followup.

phenaproxima’s picture

Assigned: phenaproxima » Unassigned

No objections to anything you did, @longwave.

phenaproxima’s picture

Assigned: Unassigned » wim leers

Since this has been cleared by @longwave, assigning back to Wim for final review.

longwave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks 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.

wim leers’s picture

Was 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 Component sources issue queue component, rather than issue's Config management. 👍)


@phenaproxima's additions of ComponentInputs::getPropSourcesWithDependency() and ComponentTreeItem:: updatePropSourcesOnDependencyRemoval() look fantastic (plus, the test coverage of course). The latter new method will undoubtedly come in handy when updating the Pattern and PageRegion config entities. Created #3524345: Avoid deleting `PageRegion`s & `Pattern`s when uninstalling field type-providing module: implement `::onDependencyRemoval()` , replace with default StaticPropSource for that.

wim leers’s picture

Title: When a field used by a content template is deleted, any inputs in that template which refer to that field should be replaced with static default values » When a field instance used by a `ContenTemplate` is deleted, or a field-type providing module is uninstalled, replace affected `inputs` with default `StaticPropSource`
Assigned: Unassigned » longwave

Cross-posted with @longwave in #43 — obviously we agree 😁

Updating title, because thanks to ::testRemoveFieldTypeProviderModule(), @phenaproxima actually solved more while at it 🥳

wim leers’s picture

Status: Reviewed & tested by the community » Fixed
wim leers’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.