feat(ui): Explore redesign - stacking browse+filter query bar, chips, cards#28892
feat(ui): Explore redesign - stacking browse+filter query bar, chips, cards#28892harshach wants to merge 16 commits into
Conversation
Browse location and quick filters now stack into one removable query: - New browsePath URL param holds the tree location (category/serviceType/ service/database/schema); it ANDs with the dropdown quickFilter so browsing never clears filters and vice versa (single combine point in performFetch; export + facet fetches included). - Persistent QUERY bar (ExploreQueryFilterChips): tinted browse chips + filter chips, each removable (browse removal truncates deeper levels), right-aligned Clear, empty-state placeholder. - Filter dropdowns apply immediately (no Update button) with helper text, compact menu, "+" selected state, and facet options that exclude their own field so unselecting a type reveals the other available types in scope. - Tree: categories that cannot contain the selected asset types gray out and collapse; counts render at every level; selection highlight follows the browse chips (deepest-loaded-ancestor fallback for deep links/reloads). - Cards: compact white layout (5-6 per viewport), single-line breadcrumb with middle-ellipsis click-to-expand, border-only selected state, stroke icon set (ExploreIconUtils) scoped to explore surfaces. - Human-readable entity-type labels via canonical-casing resolver (aggregations return lowercase keys, EntityType enum is camelCase). - Defensive aggregation access so malformed search responses degrade to empty lists instead of error toasts. - Tests: new ExploreQueryBar.spec (persistent bar, filter-survives-tree-click, graying); explore spec helpers switched to testid locators (count badges broke exact-text matching); 124 unit tests green across 11 suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
✅ PR checks passedThe linked issue has a description and all required Shipping project fields set. Thanks! |
🔴 Playwright Results — 3 failure(s), 11 flaky✅ 4307 passed · ❌ 3 failed · 🟡 11 flaky · ⏭️ 88 skipped
Genuine Failures (failed on all attempts)❌
|
- Restore the dropdown search input unconditionally: hiding it for small option sets broke checkExploreSearchFilter/searchAndClickOnOption, which every Entity.spec Tier/Certification/Tag/Glossary flow fills (~100 tests). - Keep selected facet values visible in immediate-apply mode: own-field- excluded aggregations may not return the selected value in the top-N, and non-independent mode dropped selections missing from options (ExploreTree glossary/columns specs). - Make every explore-context update-btn click conditional via a shared clickUpdateButtonIfVisible helper, arming response waits before the option click so both legacy and immediate-apply modes are satisfied (checkExploreSearchFilter, ExploreSortOrderFilter, DomainFilterQueryFilter, SearchExport, NestedColumnsExpandCollapse). - Mock the new @untitledui icons in ExploreV1.test (XClose/XCircle/ FilterFunnel01/Trash01) — the always-rendered QUERY bar needs them. - Add a regression unit test for selected-option visibility. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds coverage for the composable-query semantics of the redesigned Explore page, anchored on the Tier1-OR-Tier2 scenario: - ExploreFilterComposition.spec.ts (new E2E): tiered fixtures across table/dashboard/topic verify OR within a field (Tier1 OR Tier2 union fires ONE must clause with two should terms and surfaces assets of either tier), chip removal narrowing the union, AND across fields (tier + tag), and asset-type union composing with tier union. Facet options re-fetch only on dropdown open, so option picks retry by reopening to absorb indexing lag. - ExplorePureUtils.test.ts: unit-level OR-within / AND-across / empty- field assertions on getBrowsePathQueryFilter. - ExploreQuickFilters.test.tsx: facet aggregations keep sibling-field selections (tier terms scope the tags facet) while excluding the facet's own field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sv-se (Swedish) locale arrived from main (#28973) after the explore-redesign keys were added, so it lacked browse-estate, in, browse-estate-query-placeholder, and pick-values-to-refine. Running `yarn i18n` backfills them (English fallback for later translation), clearing the I18n Sync checkstyle diff. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ExploreDiscovery "showDeleted is on" (shard 3): the owner and domain facet steps still clicked the removed update-btn (armed the query response after the option click). Arm before the option click, use clickUpdateButtonIfVisible, and Escape to close each dropdown before opening the next — immediate-apply keeps the menu open and each open menu owns its own search-input. - Users "Check permissions for Data Steward" (shard 6): the prior fix closed the Data Assets dropdown with Escape, but ExploreV1's document-level Escape handler also closes the auto-opened summary panel, so the entity-link this step clicks vanished and the test hung to timeout. Close the dropdown by toggling its trigger instead, which leaves the summary panel (driven by searchResults) intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-redesign # Conflicts: # openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx # openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreV1.component.tsx # openmetadata-ui/src/main/resources/ui/src/pages/ExplorePage/ExplorePageV1.component.tsx # openmetadata-ui/src/main/resources/ui/src/utils/ExploreUtils.tsx
…se i18n The redesign dropped the explore toolbar's "Clear All" button, but the shared checkExploreSearchFilter helper (Entity.spec across every entity type) and SearchSeparationSuite still click data-testid="clear-filters" after applying a Tier/Certification/Tag/Glossary filter. With it gone, ~80 tests across CI shards 3/4/5 hung to timeout on the missing locator. Restore the toolbar Clear All verbatim from main (gated on active quick filters / browse path / SQL query), wired to the same clearFilters the QUERY-bar Clear uses. The design keeps both controls. Add ExploreV1 unit tests locking the clear-filters contract (absent with no filters, present with an active browse filter). Also sync the new SSO i18n keys into the sv-se locale (yarn i18n), clearing the I18n Sync checkstyle diff. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code Review 👍 Approved with suggestions 4 resolved / 5 findingsRedesigns the Explore page with a unified, composable query bar for tree browsing and facet filtering. Refined the query response waiter to use strict matching instead of loose substring checks for improved test reliability. 💡 Edge Case: Loose substring match in query_filter response waiterIn ✅ 4 resolved✅ Edge Case: Browsing a category root grays out & collapses sibling categories
✅ Bug: Domain facet key may not be lowercased like tagFQN buckets
✅ Quality: parseBrowsePathFields accepts any JSON array without shape validation
✅ Edge Case: Gold certification step omits Escape before asserting visibility
🤖 Prompt for agentsOptionsDisplay: compact → Showing less information. Comment with these commands to change:
Was this helpful? React with 👍 / 👎 | Gitar |
| ); | ||
| }; | ||
|
|
||
| const isBrowsePathField = ( | ||
| field: unknown | ||
| ): field is ExploreQuickFilterField => { | ||
| const record = | ||
| field && typeof field === 'object' | ||
| ? (field as Record<string, unknown>) | ||
| : undefined; | ||
|
|
||
| return ( | ||
| typeof record?.key === 'string' && | ||
| (record.value === undefined || Array.isArray(record.value)) | ||
| ); | ||
| }; | ||
|
|
||
| /** | ||
| * The browse location selected in the explore tree, kept in its own | ||
| * `browsePath` URL param (ordered ExploreQuickFilterField[] — category, | ||
| * serviceType, service, database, schema). It ANDs with the dropdown | ||
| * `quickFilter`, so browsing never clears filters and vice versa. | ||
| * The param is untrusted URL input — malformed elements are dropped so |
There was a problem hiding this comment.
isBrowsePathField doesn't validate value array elements
The validator confirms value is an array but never checks its elements. Any downstream call that reads option.key — including getExploreQueryFilterMust (filterValue.key.toLowerCase()) and getBrowsePathSignature (option.key.toLowerCase()) — will throw a TypeError: Cannot read properties of null (reading 'key') when an element is null, a number, or any non-object primitive.
The function comment ("malformed elements are dropped") is the stated contract but is not delivered. If a user shares a crafted explore URL such as browsePath=[{"key":"entityType","value":[null]}], anyone opening it will see the tree/page crash with an unhandled exception. Adding an element-level type guard (e.g., typeof item === 'object' && item !== null && typeof item.key === 'string') inside isBrowsePathField would close the gap.
|



Describe your changes:
Fixes #28930
This PR redesigns the Explore page around one idea: browsing and filtering compose into a single, visible, removable query. The tree's browse location now lives in its own
browsePathURL param and ANDs with the dropdownquickFilter, so clicking a service never clears your Type filter (and vice versa); everything you pick shows in a persistent QUERY bar as removable chips. Filter dropdowns apply immediately (no Update button), show facet options that exclude their own field, and use human-readable labels; incompatible tree categories gray out and collapse when an asset type is selected. Result cards get a compact white layout (5–6 per viewport), single-line middle-ellipsized breadcrumbs, a border-only selected state, and a stroke icon set scoped to explore surfaces.Type of change:
High-level design:
query = browsePath (tree location) AND quickFilter (facets) AND queryFilter (advanced).browsePathserializes the exactExploreQuickFilterField[]shape the tree already builds per level; it is combined at a single injection point inExplorePageV1.performFetch(also export modal + facet fetches + the SWR cache key), keepingquickFilter100% backward compatible for deep links.ExploreTree): hierarchical levels (category/serviceType/service/database/schema) emit the browse path; entity-type leaves emit path prefix + a Type upsert into the Data Assets slot — both land in ONE navigation. Highlight follows the chips (deepest-loaded-ancestor fallback), disabled categories collapse via controlledexpandedKeys, counts render at every level.ExploreQueryFilterChips): persistent; tinted browse chips with hierarchical removal (truncateBrowsePath), filter chips, right-aligned Clear, empty-state placeholder.SearchDropdown/ExploreQuickFilters): opt-inimmediateApply(default off — 14 other consumers unchanged); per-facet filter excludes the facet's own field (design'sfacetOptionssemantics); canonical entity-type casing resolver bridges lowercase aggregation keys vs camelCase enum.quickFilterparam — rejected because a flat ES bool query loses level provenance/ordering, making browse chips and hierarchical removal unrecoverable, and DB/schema terms aren't dropdown items so they'd be silently dropped on the next facet apply./v1/search/query+/v1/search/aggregatecover everything (verified againstSearchResourceITcoverage).Tests:
Use cases covered
browsePathhighlight the deepest loaded ancestor; legacyquickFilter-only URLs behave unchanged.Unit tests
ExplorePureUtils.test.ts(browsePath parse/build/truncate, canonical casing, node-by-path, graying),ExploreQueryFilterChips.test.tsx,AdvancedSearchPureUtils.test.ts, extendedSearchDropdown.test.tsx,ExploreTree.test.tsx,ExploreSearchCard.test.tsx,ExploreQuickFilters.test.tsx— 124 tests green across 11 suites.Backend integration tests
SearchResourceITalready covers the aggregate/query_filter paths used).Ingestion integration tests
Playwright (UI) tests
playwright/e2e/Features/ExploreQueryBar.spec.ts(persistent bar, filter-survives-tree-click with chip removal + Clear, asset-type graying — 3/3 green locally). Updated explore helpers/specs for the immediate-apply UX and testid-based tree locators (count badges broke exact-text matching).Manual testing performed
yarn start; verified the full hero flow end-to-end in Chromium (scripted + interactive): persistent QUERY bar, no Update button, chip stacking/removal, graying + collapse, facet refresh scoped to the selected service, card density/breadcrumb ellipsis, icons.UI screen recording / screenshots:
Will attach before/after screenshots and a short recording as a PR comment.
Checklist:
Fixes #28930(title kept descriptive per feat-style convention)Fixes #28930above🤖 Generated with Claude Code
Greptile Summary
This PR redesigns the Explore page around a composable query model: browsing the left-hand tree and selecting facet filters now compose into a single persistent QUERY bar of removable chips, with browse location kept in a dedicated
browsePathURL param that ANDs with the existingquickFilterwithout clobbering it.browsePathURL param & pure utils (ExplorePureUtils.ts):parseBrowsePathFields,getBrowsePathQueryFilter,truncateBrowsePath,findTreeNodeKeyByBrowsePath,getDisabledExploreTreeKeys, andgetCanonicalEntityTypeimplement the full browse-path lifecycle; injected at a single point inperformFetchso tab counts, search, and NLQ queries are all scoped to the browse location.ExploreQueryFilterChips(new component): persistent bar rendering tinted hierarchical browse chips and quick-filter chips with individual removal and a right-aligned Clear All.SearchDropdownimmediateApplymode: opt-in flag firesonChangeon every checkbox click and hides the Update button; all 14 existing consumers are unchanged (defaultfalse).Confidence Score: 4/5
Safe to merge after fixing the isBrowsePathField validator; all other changes are additive or behind opt-in flags.
The
isBrowsePathFieldvalidator inparseBrowsePathFieldschecks thatvalueis an array but never validates its elements. Downstream functions (getExploreQueryFilterMust,getBrowsePathSignature) call.key.toLowerCase()on each element without a null guard, so a shared URL containingbrowsePath=[{"key":"entityType","value":[null]}]crashes the Explore page with aTypeErrorfor every user who opens it. The fix is small and confined to a single type guard, and every other part of the redesign is correct.openmetadata-ui/src/main/resources/ui/src/utils/ExplorePureUtils.ts — specifically the isBrowsePathField element validation gap
Important Files Changed
Comments Outside Diff (1)
openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreTree/ExploreTree.tsx, line 446-461 (link)When a type filter disables a root category,
visibleExpandedKeysfilters out those root keys before passing them to the Tree. Ant Design callsonExpandwith only the keys visible to the Tree, sosetExpandedKeys(keys)permanently removes the disabled keys from state. Once the type filter is cleared and the category is re-enabled, the category stays collapsed — the user must manually re-expand it.A minimal fix is to preserve the removed keys across the disable/re-enable cycle by merging them back in
onExpand:Reviews (4): Last reviewed commit: "fix(ui): restore explore toolbar Clear A..." | Re-trigger Greptile