Depends on: #3621943: Fix exposed filters saving with no identifier This defect is specifically about that fix's own interaction with a pre-existing shallow-merge behaviour, so it should land after that one.

Problem/Motivation

Found by an independent code review of this module's own recently-merged changes, specifically checking whether the exposed-filter identifier auto-default could itself hide a different problem. It could: a partial edit to an already-exposed filter's expose options - for example, changing only the label - silently drop any other already-set key in that sub-array, most importantly a previously-set custom identifier.

Before the identifier auto-default existed, this at least failed View::validate() ("identifier required"), so the loss was at least visible. After it, the dropped identifier was silently replaced with the field ID instead - a plausible-looking but wrong value, with no error anywhere. This makes the auto-default fix strictly worse for this one scenario: it papers over a real data-loss bug instead of surfacing it.

Steps to reproduce

  1. Expose a filter with a custom identifier, e.g. {"exposed":true,"expose":{"identifier":"my_custom_id","label":"Active"}}.
  2. Later, send a partial update that only touches the label: {"expose":{"label":"Is Active"}}.
  3. Load the View and inspect the filter's saved expose.identifier.

Expected: identifier remains my_custom_id; only label changes.

Actual (before this fix): identifier silently becomes the field ID instead - the custom identifier is lost, with no error or warning anywhere.

Root cause

execute()'s $options + $configuration merge (used to combine the LLM-supplied options with the handler's existing configuration) is shallow and top-level only. When $options['expose'] is present - even if it only names one key, like label - PHP's + operator takes the entire left-hand array for that key, wholesale-replacing $configuration['expose'] rather than merging the two sub-arrays together. Any key present in the existing expose array but absent from the partial edit - most importantly identifier - is simply gone from the result.

Proposed resolution

Merge the expose sub-array itself first, before the top-level merge, so a partial edit only changes the keys it actually names:

// Merge "expose" itself first: $options + $configuration below is a
// shallow merge, so a partial "expose" edit would otherwise wipe out
// other already-set keys in it, like a custom "identifier".
if (isset($options['expose']) && isset($configuration['expose'])) {
  $options['expose'] += $configuration['expose'];
}
$final_options = $options + $configuration;

Remaining tasks

  • File this issue on drupal.org, noting the dependency above.
  • Open an MR with the fix above, based on the identifier auto-default fix branch.

Test coverage added locally

Regression test testPartialExposeEditPreservesCustomIdentifier() in tests/src/Kernel/Plugin/AiFunctionCall/UpdateViewHandlerOptionsTest.php exposes a filter with a custom identifier, then sends a label-only partial edit, and asserts the custom identifier survives untouched while the label updates correctly.

Full suite with this fix applied (isolated on its own branch, based on the identifier-default fix branch): 11/11 kernel tests passing (89 assertions) in UpdateViewHandlerOptionsTest, no failures.

API changes

None. This is a bug fix to existing, already-broken behaviour - no new context parameters, no signature changes, no config schema changes.

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

jibran created an issue. See original summary.

jibran’s picture

Issue summary: View changes
Status: Active » Needs review

  • jibran committed b3ee04fc on 1.0.x
    task: #3621965 Fix a partial 'expose' edit dropping a previously-set...
jibran’s picture

Status: Needs review » Fixed

Committed and pushed to 1.0.x

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.