Skip to content

fix(native-filters): persist created/pasted default values in select filter#40984

Open
yousoph wants to merge 2 commits into
apache:masterfrom
preset-io:fix-native-filter-creatable-default
Open

fix(native-filters): persist created/pasted default values in select filter#40984
yousoph wants to merge 2 commits into
apache:masterfrom
preset-io:fix-native-filter-creatable-default

Conversation

@yousoph

@yousoph yousoph commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Fixes three compounding bugs that prevented native filter default values from persisting when typed or pasted (rather than selected from the dropdown) in a creatable multi-select filter.

Bug: A Value (filter_select) native filter with Allow creation of new values enabled silently dropped default values that were typed or pasted. The chips appeared in the config modal but after clicking Save the filter rendered empty on the dashboard, and reopening the config showed no default value.

Fix 1 — Select.tsx: trim whitespace from pasted tokens (primary fix)

"10107, 10121".split(",") previously produced ["10107", " 10121"] — the leading space caused label-match failure against the existing option {value: 10107, label: "10107"}, resulting in the value being stored as a new string literal " 10121" rather than the correct matched option value.

- const array = token ? uniq(pastedText.split(token)) : [pastedText];
+ const array = token
+   ? uniq(pastedText.split(token).map(s => s.trim()).filter(Boolean))
+   : [pastedText.trim()].filter(Boolean);

Fix 2 — SelectFilterPlugin.tsx: multi-select typed values in options (secondary fix)

!multiSelect guard prevented typed search terms from appearing as selectable new options in multi-select filters. Also fixed a mutation bug where unshift was called on the memoized uniqueOptions array.

- if (search && !multiSelect && !hasOption(search, uniqueOptions, true)) {
-   uniqueOptions.unshift({ label: search, value: search, isNewOption: true });
- }
- return uniqueOptions;
+ if (search && !hasOption(search, uniqueOptions, true)) {
+   return [{ label: search, value: search, isNewOption: true }, ...uniqueOptions];
+ }
+ return uniqueOptions;

Fix 3 — SelectFilterPlugin.tsx: include created values in uniqueOptions (tertiary fix)

uniqueOptions was built only from DB data. When filterState.value contained created values not in the dataset, they were absent from options and relied on the Select component's internal fullSelectOptions fallback which could lose them across re-renders.

+ if (creatable !== false && filterState.value) {
+   const existing = new Set(allOptions);
+   ensureIsArray(filterState.value)
+     .filter(v => v !== null && v !== undefined && !existing.has(v))
+     .forEach(v => {
+       baseOptions.push({ label: String(v), value: String(v), isNewOption: true });
+       existing.add(v);
+     });
+ }

Test plan

  • Reproduce with Vehicle Sales dataset, order_number column — paste 10107, 10121, 10134, 10145, 10159, 10168, 10180, 10188, 10201, 10211 as default value, Save, verify filter chips appear on dashboard
  • Verify individually typing and pressing Enter also works for multi-select creatable filters
  • Verify selecting from dropdown still works correctly
  • Run jest Select.test.tsx — new test 'trims whitespace from pasted comma-separated values' should pass
  • No regression in single-select creatable filter behavior

🤖 Generated with Claude Code

yousoph and others added 2 commits June 11, 2026 17:31
Three bugs prevented default values typed/pasted into a creatable
native filter from being stored and rendered correctly:

1. Paste tokenization did not trim whitespace, so "10107, 10121"
   split into ["10107", " 10121"] — the leading space caused label
   mismatch and stored wrong option values.

2. The options useMemo in SelectFilterPlugin guarded new-option
   injection with !multiSelect, so typed search terms never appeared
   as selectable options in multi-select mode.  Also fixed the
   mutation bug (unshift on a memoized array).

3. uniqueOptions excluded values from filterState.value that are not
   in the dataset (i.e., user-created values).  Added them back when
   creatable !== false so the Select can display them correctly.

Adds a test for the whitespace-trimming fix.
The options useMemo was missing the `creatable !== false` check after the
multi-select guard was removed, allowing new option entries to appear in
the dropdown even when creatable=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dosubot dosubot Bot added the dashboard:native-filters Related to the native filters of the Dashboard label Jun 11, 2026
Comment on lines +340 to 342
if (search && creatable !== false && !hasOption(search, uniqueOptions, true)) {
return [{ label: search, value: search, isNewOption: true }, ...uniqueOptions];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The new-option injection condition does not check searchAllOptions, so a "create new" option is still shown when remote search mode disables creation (allowNewOptions={!searchAllOptions && creatable !== false}). In that mode users can pick a synthetic value that should not be creatable, causing inconsistent filter state and incorrect queries. Add the same !searchAllOptions guard here so option creation behavior matches the Select props. [incorrect condition logic]

Severity Level: Major ⚠️
- ⚠️ Remote-search value filters still allow synthetic created options.
- ⚠️ Dashboards may apply filters on non-existent, user-typed values.
Steps of Reproduction ✅
1. In the native filter configuration UI implemented by `FiltersConfigForm.tsx` (see
`controlsOrder` including `'creatable'` and `'searchAllOptions'` at
`superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx:149-154`),
create a `filter_select` filter, leave `creatable` at its default `true`, and enable
`searchAllOptions` so that remote search is active.

2. Load a dashboard with this filter, which renders the `PluginFilterSelect` component
(`superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx:119-151`) and
passes `formData.searchAllOptions` and `formData.creatable` into the component; note that
the Select control is configured with `allowNewOptions={!searchAllOptions && creatable !==
false}` at lines 600-605, disabling built-in new-option creation when `searchAllOptions`
is true.

3. Type a value into the filter's search box that is not present in the current option
list while `searchAllOptions` remains true; `onSearch` at lines 265-279 updates the local
`search` state and, because `searchAllOptions` is true, dispatches `ownState.search` for
remote querying, but regardless of remote results the component's local `search` variable
now contains the typed text.

4. Because `options` is computed at lines 339-344 without checking `searchAllOptions`
(only `search && creatable !== false && !hasOption(search, uniqueOptions, true)`), the
typed term is injected as `{ label: search, value: search, isNewOption: true }` even when
`searchAllOptions` is true; the `Select` at lines 600-633 receives this synthetic option
in its `options` prop despite `allowNewOptions` being false, so the user can select a
"created" value in a mode where creation should be disabled, leading to inconsistent
filter state and queries built via `updateDataMask` at lines 216-245 containing that
synthetic value.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
**Line:** 340:342
**Comment:**
	*Incorrect Condition Logic: The new-option injection condition does not check `searchAllOptions`, so a "create new" option is still shown when remote search mode disables creation (`allowNewOptions={!searchAllOptions && creatable !== false}`). In that mode users can pick a synthetic value that should not be creatable, causing inconsistent filter state and incorrect queries. Add the same `!searchAllOptions` guard here so option creation behavior matches the Select props.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.28571% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.30%. Comparing base (74845ea) to head (f03ff98).

Files with missing lines Patch % Lines
...c/filters/components/Select/SelectFilterPlugin.tsx 63.63% 4 Missing ⚠️
.../superset-ui-core/src/components/Select/Select.tsx 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40984      +/-   ##
==========================================
- Coverage   64.30%   64.30%   -0.01%     
==========================================
  Files        2657     2657              
  Lines      144060   144070      +10     
  Branches    33216    33219       +3     
==========================================
+ Hits        92641    92647       +6     
- Misses      49797    49801       +4     
  Partials     1622     1622              
Flag Coverage Δ
javascript 68.24% <64.28%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Agent Run #5cfb3b

Actionable Suggestions - 1
  • superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx - 1
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx - 1
Review Details
  • Files reviewed - 3 · Commit Range: 9667ae5..f03ff98
    • superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx
    • superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx
    • superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines +693 to +695
const array = token
? uniq(pastedText.split(token).map(s => s.trim()).filter(Boolean))
: [pastedText.trim()].filter(Boolean);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paste handling inconsistency

The paste handler in Select.tsx now trims whitespace and filters empty strings from token-separated values, but AsyncSelect.tsx (line 673) retains the original unprocessed split logic. This inconsistency means users pasting identical text into these two components receive different tokenization results. Align AsyncSelect.tsx with the corrected behavior in Select.tsx.

Code Review Run #5cfb3b


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard:native-filters Related to the native filters of the Dashboard packages size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant