Skip to content

feat(ui): render Explore quick filters through FilterSelect - #33021

Merged
harsh-vador merged 35 commits into
mainfrom
feat-explore-quick-filters-swap
Sep 17, 2026
Merged

harsh-vador merged 35 commits into
mainfrom
feat-explore-quick-filters-swap

Conversation

@harsh-vador

@harsh-vador harsh-vador commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Describe your changes

Stacked on #33011 (FilterSelect primitive) and #33017 (display fixes) — review only the top commits (feat(ui): render Explore quick filters through FilterSelect and FilterSelect icon accepts rendered nodes); the rest belong to the parent PRs. Draft until both parents merge, then this rebases to a clean diff.

Replaces both quick-filter renderers in ExploreQuickFilters with the unified FilterSelect core component:

  • the AntD SearchDropdown path, and
  • the untitledDropdown fork (QuickFilterDropdown), which existed only because AntD popups go inert inside SlideoutMenu drawers — react-aria popovers don't, so the fork is deleted.

One renderer now covers Explore, Assets tabs (Glossary/Domain/Data Product/Tag/Team/Lineage), the Add Assets drawer, Lineage controls, Domains & Data Products listings, Test Definitions, and Column Bulk Operations — giving all of them the uniform checkbox/typography rows and, on multi-select filters, the Select All row (#29569).

What did NOT change

  • Option fetching: page-aggregation reuse, per-facet queries, sourceFields top-hits casing, the request-id race guard — untouched. The swap is presentational.
  • Filter values and URL round-trip semantics.
  • SearchDropdown itself stays — its 8 direct consumers (TableQueries, AuditLog, DataInsight, DQ dashboard, …) migrate in follow-up PRs.

Details

  • Search keystrokes now debounce (500 ms) in ExploreQuickFilters — the legacy dropdowns debounced internally, FilterSelect reports each keystroke.
  • onChange maps selected values back to their existing option objects so hydrated labels survive; the null option maps to OM_NULL_FIELD exactly as before.
  • FilterSelect gained two small additions for this (in the feat(ui-core): add FilterSelect — unified filter dropdown primitive #33011 branch): icon accepts already-rendered nodes (entity/service icons are pre-rendered elements) and a helperText slot (Explore's "pick values to refine").
  • Trigger keeps the search-dropdown-<key> testid. Menu-internal E2E selectors (search-input, update-btn, checkbox rows) change with the DOM — Playwright specs will be updated based on CI results in this PR.
Screen.Recording.2026-09-11.at.5.24.33.PM.mov

Testing

  • ExploreQuickFilters unit suite rewritten against the FilterSelect surface: 39/39 pass (fetch behavior, race guards, facet scoping, static options, null option, selection mapping all preserved).
  • FilterSelect suite: 16/16. Type-check clean in changed files; ESLint 0 errors.

🤖 Generated with Claude Code

Greptile Summary

The PR consolidates Explore quick-filter rendering on the shared FilterSelect component and updates its consumers and test coverage.

  • Replaces the legacy Explore quick-filter dropdown paths with FilterSelect while preserving staged and immediate selection modes.
  • Adds single-select staging, compatibility selectors, non-modal popover dismissal, search lifecycle handling, and scroll-position safeguards.
  • Updates unit and Playwright tests for the new roles, selectors, and interaction model.
  • Extends Playwright impact mapping so shared FilterSelect changes select affected consumer specifications.
Confidence Score: 5/5

The PR appears safe to merge because no new actionable defect remains and all previous Greptile threads are resolved.

The changes since the previous review only adjust a Playwright helper to target the migrated FilterSelect row, and the current popover lifecycle does not establish a realistic duplicate-locator failure in that flow. All previous findings were manually resolved, so none remain outstanding.

Important Files Changed
Filename Overview
openmetadata-ui-core-components/src/main/resources/ui/src/components/application/filter-select/filter-select.tsx Extends the shared filter primitive with staged single selection, legacy automation hooks, non-modal dismissal, and updated option presentation.
openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx Migrates Explore quick filters to FilterSelect while retaining existing fetching and selection mapping.
openmetadata-ui-core-components/src/main/resources/ui/src/components/foundations/typography.tsx Adds inline Typography rendering for text embedded in buttons and flex-row controls.
.github/playwright/impact-map.json Routes shared FilterSelect and ExploreQuickFilters changes to their affected end-to-end specifications.
openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts Updates the steward-permission helper to select the FilterSelect option row through its compatibility test IDs.
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  C[Explore and asset consumers] --> EQF[ExploreQuickFilters]
  EQF --> FS[Shared FilterSelect]
  FS --> T[Trigger and selected count]
  FS --> S[Search and fetched options]
  FS --> M[Single or multiple selection]
  M -->|Immediate| U[Update consumer filters]
  M -->|Staged| A[Apply or cancel]
  A --> U
Loading

Reviews (17): Last reviewed commit: "test(playwright): migrate the last hidde..." | Re-trigger Greptile

@github-actions github-actions Bot added the UI UI specific issues label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@harsh-vador harsh-vador added the safe to test Add this label to run secure Github workflows on PRs label Sep 9, 2026
@harsh-vador harsh-vador self-assigned this Sep 9, 2026
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 72%
72.92% (102742/140882) 57.78% (62412/108001) 58.94% (20420/34642)

…ws (#33378)

* style(ui-core): brand-blue selected state for single-select filter rows

Design feedback on the unified filter dropdowns: a single-select row's
selected state reads as blue — brand-50 row tint with brand text, icon and
count pill — instead of a trailing tick. The tick is removed; multi-select
rows are unchanged, where the checkbox alone conveys selection.

Verified live in Storybook: the selected row renders one svg (the option's
own icon — no tick), rgb(239,248,255) row tint, and rgb(21,112,239) icon and
label.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ui-core): cover the icon and count-pill selected styling paths

Review follow-up: the single-select styling test used bare options, so the
svg recolor override and the pill's brand border/text had no coverage. The
new case selects an icon-and-count option and asserts both selected-state
classes, and their absence on an unselected sibling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Comment thread .github/playwright/impact-map.generated.json
harsh-vador and others added 2 commits September 16, 2026 13:09
- Staged single-select waits for Apply again (greptile P1): FilterSelect
  now stages single selections when commitMode='staged' and keeps the
  popover open (shouldCloseOnSelect is owned by the component), restoring
  the legacy Update-gated behavior for consumers like Test Definitions.
- Staged labels survive option repaints (greptile P2): ExploreQuickFilters
  records every option it publishes per field (bounded map) and resolves
  committed keys against it, so a staged value keeps its human-readable
  label after a later search replaced the shared options list.
- Impact map (greptile P2): hand-authored mapping routes filter-select /
  base dropdown / ExploreQuickFilters changes to the former SearchDropdown
  consumer spec set, so PR CI selects that coverage again.
- Checkstyle: drop the unused raw title attribute from the FilterSelect
  test mock (the remaining warnings in the summary are pre-existing
  backlog in files this PR only reflows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A full clear at the 500-entry cap could evict the one entry a still-staged
key needs, reintroducing the raw-key-as-label fallback the record exists to
prevent. Delete-then-set keeps Map insertion order as recency order, and
overflow now drops single oldest entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

This comment was marked as outdated.

harsh-vador and others added 3 commits September 16, 2026 14:22
…nstantly

The impact-map entry added for #33021 review feedback now routes the former
SearchDropdown consumer specs to this PR — their first run against
FilterSelect surfaced two classes of failure:

1. Legacy selectors in specs CI had never selected here: Update-button-by-
   name is now the update-btn testid ('Apply (N)' label), option rows are
   menuitemcheckbox rather than menuitem, and the search box's accessible
   name is no longer the legacy 'Search <label>...' placeholder. Migrated
   ImpactAnalysis, ColumnBulkOperations, DomainFilterQueryFilter,
   ExploreTree, and the entity/glossary utils.

2. The popover exit animation kept the closing dropdown visible and
   hit-testable for ~100ms, so helpers that poll drop-down-menu visibility
   (ExploreUrlState, ExploreFilterComposition) filled the dying filter's
   now-disabled search box and hung until their predicate timeout — the
   trace shows zero tag aggregations fired. FilterSelect popovers now close
   with no exit animation (hidden + animate-none), so the closing subtree
   is gone the instant it closes; verified in Storybook (0 menus 30ms after
   Escape, exactly one menu and one enabled search box mid sibling-swap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	.github/playwright/impact-map.generated.json
- QueryFilterInterface is defined in interface/queryFilter.interface.ts;
  import it from there instead of through the ExplorePage re-export
  (no-lower-layer-page-imports in ExploreQuickFilters and ExploreV1).
- ExploreV1 read the *global* location, which is not a valid hook
  dependency — the parsedSearch memo never recomputed on in-app
  navigation. Use useCustomLocation like its siblings.
- Complete the exhaustive-deps arrays where every added dependency is
  identity-stable (stable useCallback, derived-from-existing-dep values,
  a prop, i18n t) and memoize getTabsInfo() so it can be one.

Remaining two warnings are runtime import cycles through SearchClassBase
and AdvanceSearchProvider that predate this PR and need a dependency
inversion, not a local edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
harsh-vador and others added 6 commits September 16, 2026 14:53
getTabsInfo() bakes t() output into its labels, so memoizing it with no
dependencies froze the first language for the component's lifetime
(greptile P2 on #33021). Key the memo on i18n.language; the local
react-i18next test mock now returns i18n like the global one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The input-variant trigger was a fixed 176px at ~40px tall, so toolbars
that swapped an antd Select for FilterSelect (Collate pipeline
observability filters) grew taller, stopped filling their flex items, and
wrapped to a second row. Default to 32px height and full container width
— matching the replaced control — and let consumers constrain via
className.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…elect

The wider (push-trigger) Playwright run selects specs the PR-scoped run
does not, surfacing four more legacy-renderer assumptions:

- Add-Assets drawer matrix asserted the trigger echoes the selected label;
  FilterSelect shows a count badge — assert the badge instead (applied =
  '1', discarded = hidden).
- ImpactAnalysis owner/domain rows were located by label association,
  which react-aria menu rows don't have — use role menuitemcheckbox by
  accessible name.
- Pagination's Impact Analysis quick filter still clicked the Update
  button by visible name.
- The glossary asset-modal util used the legacy label-cased checkbox id
  (Mysql-checkbox); FilterSelect keys rows by option value, and the ES
  serviceType bucket key is normalized to lowercase (mysql-checkbox).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ctAnalysis

The ui-checkstyle gate replays organize-imports → eslint --fix → prettier
and fails when the tree moves; the hand-reflowed getByRole chains and
import block differed from that pipeline's stable output. This is the
sequence's own result, iterated to a fixed point locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	.github/playwright/impact-map.generated.json
harsh-vador and others added 3 commits September 16, 2026 20:21
The empty state, helper text, and status-footer count were raw spans/divs
with ad-hoc size and color classes; use the foundations Typography
component. The structural spans inside the trigger, option rows, and
chips stay: Typography wraps content in a block-level prose div, which
would break their inline flex flow, truncation, and the legacy
.locator('span') E2E contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… state

The FilterSelect option rows kept a hidden real <input> solely so legacy
specs could .check()/toBeChecked() a `<value>-checkbox|-radio` id. The
design-system checkbox (CheckboxBase via Dropdown.Item showCheckbox)
already renders the visuals, and react-aria already exposes the state as
aria-checked on the menuitemcheckbox/menuitemradio row that carries the
same bare-value testid — so the shim is gone and every spec now asserts
aria-checked on the row and clicks the row itself.

Central helpers keep their legacy `-checkbox` string surface and strip
the suffix (checkCheckboxStatus, applyEntityTypeFilterValue), so most
call sites are untouched; direct input references across 14 specs/utils
are migrated to row locators. The null row is addressed by its value id
(OM_NULL_FIELD) instead of no-option-checkbox.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Playwright impact map auto-refreshed

This PR touched specs or UI source that changed the source→spec routing map. I regenerated .github/playwright/impact-map.generated.json and pushed the diff to this branch.

- source entries: 777 → 777
- 0 added, 0 removed, 4 changed spec-list

Entries whose spec list changed:
  openmetadata-ui/src/main/resources/ui/src/components/Alerts/AlertFormSourceItem/AlertFormSourceItem.tsx
  openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.tsx
  openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/SqlQueryTab/AddSqlQueryFormModal/AddSqlQueryFormModal.component.tsx
  openmetadata-ui/src/main/resources/ui/src/components/SearchDropdown/SearchDropdown.tsx

What is this file? It is the auto-generated half of Playwright's PR planner. It routes "if source X changes, run specs Y" by walking spec imports and cross-referencing getByTestId strings. Hand-authored routing in impact-map.json always wins on conflict.

What if I want to regenerate locally instead? Run this before pushing your next change to skip the bot commit:

python3 .github/scripts/generate_playwright_impact_map.py
git add .github/playwright/impact-map.generated.json
git commit --amend --no-edit  # or a separate commit

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

⚠️ UI Checkstyle passed — lint findings in changed files

🔍 ESLint findings in this PR's files — 0 error(s), 2 warning(s)

Errors block the build. Warnings do not yet — they are rules whose backlog is still
being worked down, listed so this PR does not add to it. See docs/ui-code-quality-gate.md.

0 error(s), 2 warning(s) across 1 changed file(s).

Count Rule
2 openmetadata-imports/no-circular-imports
All findings
Location Rule Message
🟡 src/components/Explore/ExploreQuickFilters.tsx:46:1 openmetadata-imports/no-circular-imports This runtime import participates in a circular dependency. Extract the shared type/constant/utility or invert the dependency.
🟡 src/components/Explore/ExploreQuickFilters.tsx:49:1 openmetadata-imports/no-circular-imports This runtime import participates in a circular dependency. Extract the shared type/constant/utility or invert the dependency.

Fix locally (fast - only checks files changed in this branch):

make ui-checkstyle-changed

harsh-vador and others added 3 commits September 16, 2026 21:24
Typography gains an additive `inline` prop that skips its block-level
prose wrapper, making it usable inside buttons, chips, and flex rows
where a wrapping div is invalid HTML or breaks truncation — the reason
FilterSelect still carried raw text spans. All remaining text (trigger
labels, count badge, option titles, count pills, chip labels,
placeholder) now renders through Typography; structural/icon spans stay.
Also centers the empty state and drops it to text-xs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er.ts

The Data Steward permission flow still clicked the removed
table-checkbox input via a string locator, which the row-migration sweep
regex missed. Scroll and click the menu row itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 4 closed / 4 findings

Migrates Explore quick filters to the unified FilterSelect component, replacing both the AntD SearchDropdown and QuickFilterDropdown fork. Debounced search (500 ms), staged single-select support, and search-result race guards all preserved; all four resolved findings (search cancellation on close, Escape key drawer interaction, initial-options cache key, staged-label cache clearing) have been addressed.

✅ 4 closed
Edge Case: Debounced search isn't cancelled on close, defeating race guard

📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:374-388 📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:467-481 📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:128-134
debouncedSearch is a single shared instance and is never cancelled when a dropdown closes or the component unmounts. Because the request id is assigned inside getFilterOptions when the fetch actually starts, a keystroke typed in field A that fires ~500ms later (after the user closed A and opened field B) starts a newer request than B's getInitialOptions, so isLatestOptionsRequest treats it as current and writes A's results into the shared options state while B is displayed — exactly the cross-field repaint the request-id guard was built to prevent. Cancel the pending debounce whenever a dropdown transitions (e.g. call debouncedSearch.cancel() in onOpenChange, and on unmount) so a stale keystroke can't outrun the guard.

Bug: Escape in FilterSelect also closes host drawer/modal

📄 openmetadata-ui-core-components/src/main/resources/ui/src/components/application/filter-select/filter-select.tsx:516-520
The new document-level, capture-phase keydown handler calls handleOpenChange(false) on Escape but never stops the event, so it keeps propagating to the surrounding React Aria Modal/ModalOverlay (SlideoutMenu, Add Assets drawer), which dismisses on Escape too. Pressing Escape to close an open filter therefore also tears down the drawer that hosts it — a regression from the legacy fork this PR removes. Call event.stopPropagation() (and ideally event.preventDefault()) inside the Escape branch when the filter is open so the keystroke is consumed by the filter only.

Edge Case: Initial-options cache key omits isNLPActive

📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:202-209 📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:242-253
getInitialOptionsCacheKey keys entries on key/index/showDeleted/searchText/facet-filter but not on isNLPActive, yet that flag is passed into getAggregationOptions and switches the request to a completely different endpoint (/hybrid/nlq/search/aggregate vs /search/aggregate). If a user toggles NLP mode while the component stays mounted and reopens the same facet with an otherwise-identical context, the cache serves options fetched from the previous search mode. Add String(isNLPActive) to the cache key array so NLP toggles produce a distinct entry.

Edge Case: Full clear of seen-options cache can drop a staged label

📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:235-245 📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx:507-521
publishOptions clears the entire seenOptionsRef map when size + opts.length would exceed 500 (ExploreQuickFilters.tsx:238-240). Because the map is the sole fallback that lets handleChange recover a staged value's label after a later search repaints options, a session that publishes many options across successive searches in one field can evict the very entry a still-staged key depends on — at Apply the value then falls back to its raw key as the label, the exact regression this cache was added to prevent. Consider an LRU/size-bounded eviction that drops oldest entries instead of wiping the whole map (delete-then-set on re-insert, evict single oldest when over cap).

Review coverage

Rules No rules evaluated

Functional validation Not enabled · Set up

Auto-approval Not enabled · Set up

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

safe to test Add this label to run secure Github workflows on PRs UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants