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
- Expose a filter with a custom identifier, e.g.
{"exposed":true,"expose":{"identifier":"my_custom_id","label":"Active"}}. - Later, send a partial update that only touches the label:
{"expose":{"label":"Is Active"}}. - 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.
Issue fork ai_agents_views-3621965
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 #3
jibranComment #5
jibranCommitted and pushed to 1.0.x