Prevent modules from being uninstalled if they provide field types used in an Experience Builder field

Created on 30 May 2024, 7 months ago
Updated 21 June 2024, 6 months ago

Problem/Motivation

If a field used in a experience builder field there is nothing stopping the module that provides that field from being uninstalled

Steps to reproduce

  1. Use a field in experience builder field
  2. uninstall the module that provide the field
  3. view the entity that used the field
  4. Should get a 'Unhandled expression type.' exception

Proposed resolution

we'll need to implement something like `\Drupal\field\FieldUninstallValidator` instead. And thanks to the expressions, we easily can:

  1. new `\Drupal\experience_builder\FieldUninstallValidator` service
  2. assume the module being uninstalled provides field type `foobar`
  3. iterates over all XB field instances, for each field instance:
  4. determine the DB table of the _current_ data (e.g. `node__field_xb_demo`) as well as the _revision_ data (e.g. `node_revision__field_xb_demo`)
  5. determine the prop expression prefix that should not occur for this module to be safely uninstallable: `new FieldTypePropExpression('foobar', '')` (yes, use the empty string as the field prop name β€” not semantically beautiful, but pragmatic at this stage)
  6. determine which entities use an instance of the `foobar` field type inside`XB`: `SELECT entity_id, JSON_SEARCH(field_xb_demo_props, 'all', '`:information_source:`foobar␟%') FROM node__field_xb_demo`
  7. For all XB field instances we will also need to check the default values to ensure the field type `foobar` is not used there either

The result is _one query per entity type_ to find where XB uses instances of this field type to assign static props values.

Remaining tasks

User interface changes

API changes

Data model changes

πŸ“Œ Task
Status

Fixed

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @tedbow
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

    Add a item to the summary that we need to account for default values of XB fields

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Crediting myself for that proposed solution. I wrote that at https://git.drupalcode.org/project/experience_builder/-/merge_requests/2....

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Assigned to tedbow
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Wonderful to have you working on this, @tedbow! You'll be the first one to start using https://mariadb.com/docs/skysql-dbaas/ref/xpand/functions/JSON_EXTRACT/ and other JSON functions in the database πŸ˜„

  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

    @Wim Leers well that should be interesting!

    I have rough version of finding use of a field if it is in the default value of a field. Next I will work on the actual querying of the column values

  • Status changed to Needs work 7 months ago
  • Status changed to Active 7 months ago
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

    @Wim Leers #8, AFAICT Sqlite does not support json_search? are we going support all core DB supported databases right now? Or I am I wrong about json_search() support?

  • Status changed to Needs work 7 months ago
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

    Ok was able to find matches using json_extract() but the wildcard key matching works in MariaDB but not Sqlite

    Didn't mean to bump it down from critical before

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    #12: Why is that? https://sqlite.org/json1.html#jex says this should work? I'll get SQLite testing going…

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    FYI: working to preemptively unblock this issue by ensuring XB is being tested against new enough SQLite version: πŸ“Œ PHPUnit SQLite CI job Needs review .

  • Status changed to Postponed 7 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Status changed to Needs work 7 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    πŸ“Œ PHPUnit SQLite CI job Needs review landed. That should allow us to prove or disprove #12, and once this is merged, we'll have the confidence that it won't break.

  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    BTW, for conditionally skipping tests depending on the active DB (and hence depending on CI job), see \Drupal\KernelTests\Core\Database\DriverSpecificKernelTestBase::setUp() for inspiration.

  • πŸ‡¬πŸ‡§United Kingdom catch

    Adding a reference to ✨ JSON-based data storage proposal for component-based page building Active so it's easier to find in both directions.

  • Assigned to wim leers
  • Status changed to Needs review 7 months ago
  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA
  • πŸ‡¬πŸ‡§United Kingdom catch

    This doesn't help us if someone deletes a field - it is only addressing uninstalling a module.

    I'm a bit confused by this. Which I could possibly answer by reading the code base more, but also feels a bit conceptual so being lazy instead.

    My understanding of the current approach is:

    1. There is a 'xb json field' which holds all the data relevant to XB.

    2. This 'xb json field' holds components and props etc. which are based on 'field types'.

    3. However, the different field types that are used in the 'xb json field' don't have any kind of representation as field.storage.* field.field.* config entities assigned to the entity bundle, they are are instead dynamically used based on the components available.

    If #3 is correct, then there is no such thing as 'deleting a field' with this data model, because there is no field (in the current entity bundle + fields sense) to delete.

    If #3 is wrong, and we have to create field.storage.* and field.field.* config entities for each bundle that's using components in xb that need those field types, then the very existence of those fields should prevent uninstall (because they will impact the components available to use), not whether they have content.

    If whatever provides the components/props has an explicit dependency on the field types, then it also feels like we could use that. i.e. an image component with an image widget/formatter using the image field type being available on a bundle should prevent uninstall of the image module. To uninstall image module, you'd need to remove that component, and then removing the component would depend on whether there's any content using it.

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    @catch it's not fields, it's references in the tree to values in other fields

    Eg a hero title component might reference a hero image field and title field in a node. At present if the hero image field is deleted it fatals

    The intent is that some components store arbitrary data but some are backed by structured fields. The intention is this will allow editing the field values and the 'layout' in one place

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    In terms of the why, a hero image is a good example - you could imagine this would be used in the full page view but also in a card view mode for a node

    Having some structured data at the entity level allows for reuse

  • πŸ‡¬πŸ‡§United Kingdom catch

    Eg a hero title component might reference a hero image field and title field in a node. At present if the hero image field is deleted it fatals

    If the 'hero title component' is enabled for an entity bundle, that would also break if the field is deleted, so shouldn't we be preventing deletion of the field while the component is enabled, AND also prevent deletion of the component if it's used in any content?

  • πŸ‡ΊπŸ‡ΈUnited States tedbow Ithaca, NY, USA

    @catch @larowlan sorry we really to document better how this works

    for now I have created πŸ“Œ Prevent fields from being deleted if they are used in Experience Builder field's dynamic prop values Active because the "props" in XB field could dynamically reference an actual field of a entity being displayed. In which case we would want to prevent the field on the bundle from being deleted

    This current issue deals with a static field where it basically just uses the "shape" of a field type and stores the values in the props too(what we are calling "static"). In this case the values for the field props would be stored in the "props" column of the XB field

    • Wim Leers β†’ committed 4de875ca on sdc_prop_choices
      Remove addition by @larowlan β€” it is up to #3450957 + #3452848 to preven...
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Issue was unassigned.
  • Status changed to RTBC 6 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Pipeline finished with Skipped
    6 months ago
    #193858
  • Status changed to Fixed 6 months ago
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024