Skip to content

Fixes #26805: proper casing for Explore quick filters via sourceFields - #31307

Merged
Rohit0301 merged 18 commits into
mainfrom
explore-quick-filters-proper-casing
Aug 20, 2026
Merged

Fixes #26805: proper casing for Explore quick filters via sourceFields#31307
Rohit0301 merged 18 commits into
mainfrom
explore-quick-filters-proper-casing

Conversation

@Rohit0301

@Rohit0301 Rohit0301 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Describe your changes:

Fixes #26805

Explore quick-filter dropdowns were showing option labels in lowercase because Elasticsearch .keyword fields use a lowercase_normalizer for aggregation matching. This PR threads a sourceFields parameter through the filter pipeline so each aggregation request triggers a top_hits sub-aggregation; labels are then read from _source (original case) rather than the lowercased bucket key.

Type of change:

  • Improvement

High-level design:

Problem: getOptionsFromAggregationBucket used bucket.key (lowercase) as the display label.

Solution: The backend already supports a top_hits sub-aggregation via the sourceFields query param (used by AdvancedSearch autocomplete). This PR reuses the same mechanism for quick filters:

  • ExploreQuickFilterField interface gains an optional sourceFields?: string field (dot-path into _source).
  • All affected filter constants get sourceFields values: domains → 'domains.displayName', owners → 'ownerDisplayName' (flat field, confirmed from ES index mapping), tags → 'tags.tagFQN', tier/certification, service/database/schema, charts/tasks/data models, etc.
  • getAggregationOptions forwards sourceFields to the GET path and adds topHits: { size: 1 } on the POST path to trigger the sub-aggregation.
  • getOptionsFromAggregationBucket gains an optional sourceFields param and a private extractSourceValue helper that handles both flat and array-typed _source paths (arrays are dereferenced at [0] mid-traversal). key stays as the lowercase bucket key for filter matching; only label changes.
  • ExploreQuickFilters threads field.sourceFields through all fetch and render paths, and bypasses cached page aggregations (which lack top_hits data) when sourceFields is set.

Tests:

Use cases covered

  • Domains filter dropdown shows the original-cased domain displayName, not lowercase
  • Owner filter shows original casing via flat ownerDisplayName source field
  • Tag filter options show original FQN casing (e.g. PersonalData.Personal)
  • Tier filter options show original FQN casing
  • Service, database, schema, chart, task, and other display-name filters now also request sourceFields

Unit tests

  • Not added (logic is covered by Playwright E2E below)

Backend integration tests

  • Not applicable (no backend API changes)

Ingestion integration tests

  • Not applicable (no ingestion changes)

Playwright (UI) tests

  • Added Playwright E2E tests asserting proper casing for domains, tier, and tag filter options
  • File: playwright/e2e/Features/ExploreQuickFilters.spec.ts — new describe block 'Quick filter options - proper casing from top_hits'

Manual testing performed

  1. Open Explore page
  2. Expand Domains, Owners, Tag, Tier, Service dropdowns
  3. Confirm options show original casing (e.g. "Data Engineering" instead of "data engineering")
  4. Apply a filter — confirm the search still works (filter value remains the lowercase key)

UI screen recording / screenshots:

Not applicable (behavior change in label text, no visual layout changes).

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • My PR is linked to a GitHub issue via Fixes #26805 above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: not applicable.
  • For UI changes: screen recording not applicable (label-only change).
  • I have added tests (Playwright E2E) and listed them above.

Greptile Summary

The PR preserves lowercased aggregation keys for filtering while sourcing quick-filter labels from each bucket's original-cased _source data.

  • Threads optional sourceFields metadata through quick-filter loading and aggregation requests.
  • Extracts matching values from flat, nested, and multi-valued source fields, with bucket-key fallback.
  • Adds unit and Playwright coverage for domain, owner, tag, tier, and service labels.
  • Updates the Playwright impact map and glossary filter helpers.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/src/components/Explore/ExploreQuickFilters.tsx Threads source-field metadata through initial and searched option loading and bypasses cached aggregations that lack top-hit data.
openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts Extracts original-cased labels from flat, nested, and array-valued top-hit source data while retaining bucket keys for filtering.
openmetadata-ui/src/main/resources/ui/src/utils/ExploreUtils.tsx Requests source fields on GET aggregation calls and top hits on POST aggregation calls.
openmetadata-ui/src/main/resources/ui/src/constants/AdvancedSearch.constants.ts Configures source paths for affected quick-filter fields across Explore entity types.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts Adds end-to-end assertions for original-cased domain, owner, tag, tier, and service labels.

Sequence Diagram

sequenceDiagram
  participant User
  participant QuickFilters as ExploreQuickFilters
  participant API as Search aggregate API
  participant Search as Elasticsearch/OpenSearch
  User->>QuickFilters: Open or search a quick filter
  QuickFilters->>API: Aggregate field with sourceFields
  API->>Search: Terms aggregation plus top_hits
  Search-->>API: Lowercase bucket key and original _source
  API-->>QuickFilters: Aggregation buckets
  QuickFilters->>QuickFilters: Match source value to bucket key
  QuickFilters-->>User: Original-cased label with bucket key as value
Loading

Reviews (8): Last reviewed commit: "Merge branch 'main' into explore-quick-f..." | Re-trigger Greptile

Context used:

…s top_hits

Thread `sourceFields` through the quick-filter pipeline so each
aggregation request triggers a `top_hits` sub-aggregation, and option
labels are read from `_source` (original case) rather than the
lowercase bucket key.

- Add `sourceFields?: string` to `ExploreQuickFilterField` interface
- Add `sourceFields` to all affected filter constants (domains, owners,
  tags, tier, certification, service, database, schema, charts, tasks,
  data models, classification, glossary) across all dropdown lists;
  owners uses the flat `ownerDisplayName` field (no array traversal)
- Update `getAggregationOptions` to forward `sourceFields` to GET path
  and pass `topHits: { size: 1 }` on POST path
- Refactor `getOptionsFromAggregationBucket` to extract a private
  `extractSourceValue` helper with array-aware dot-path traversal
- Thread `sourceFields` through `ExploreQuickFilters` fetch functions
- Add Playwright tests asserting proper casing for domains, tiers, tags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the UI UI specific issues label Aug 10, 2026
@github-actions

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!

@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

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

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.

@Rohit0301 Rohit0301 self-assigned this Aug 10, 2026
@Rohit0301 Rohit0301 added the safe to test Add this label to run secure Github workflows on PRs label Aug 10, 2026
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit ba5f0a88989d13e49b31efd871dd72d478686e4d in Playwright run 32332235710, attempt 1.

✅ 1085 passed · ❌ 0 failed · 🟡 3 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 52m 57s

⏱️ Max setup 4m 46s · max shard execution 18m 12s · max shard-job elapsed before upload 22m 27s · reporting 9s

🌐 201.84 requests/attempt · 2.20 app boots/UI scenario · 22.52% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 22.52% (convergence target: at most 15%).
  • Browser traffic was 201.84 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.2 per UI scenario (2448 boots / 1114 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 125 0 0 0 0 0
🟡 Shard chromium-02 122 0 1 0 0 0
🟡 Shard chromium-03 129 0 1 0 0 0
✅ Shard chromium-04 122 0 0 0 0 0
✅ Shard chromium-05 141 0 0 0 0 0
🟡 Shard chromium-06 128 0 1 0 0 0
✅ Shard chromium-07 131 0 0 0 0 0
✅ Shard data-asset-rules-01 61 0 0 0 0 0
✅ Shard domain-isolation-01 14 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 7 0 0 0 0 0
✅ Shard ingestion-01 2 0 0 0 0 0
✅ Shard reindex-01 28 0 0 0 0 0
✅ Shard search-01 12 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 3 flaky test(s) (passed on retry)
  • Pages/Entity.spec.tsUpdate description (shard chromium-02, 1 retry)
  • Pages/Glossary.spec.tsGlossary & terms creation for reviewer as user (shard chromium-03, 1 retry)
  • Pages/Glossary.spec.tsGlossary & terms creation for reviewer as team (shard chromium-06, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

- ExploreQuickFilters.test.tsx: add missing 10th arg (sourceFields=undefined)
  to all getAggregationOptions toHaveBeenCalledWith assertions
- AdvancedSearchPureUtils.ts: fix extractSourceValue to match the correct
  array element by bucket key (case-insensitive) rather than always taking
  [0] — fixes the case where an asset has multiple domains/tags and [0]
  doesn't correspond to the current bucket
- AdvancedSearchPureUtils.test.ts: add unit tests covering flat field
  extraction, nested single-object path, array element matching by key,
  and fallback when no top_hits data is present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 66%
66.95% (80243/119846) 51.36% (49069/95535) 52.34% (14663/28012)

Rohit0301 and others added 2 commits August 17, 2026 14:05
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
aniketkatkar97
aniketkatkar97 previously approved these changes Aug 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-17T12:40:00Z)

Blocked the queue: playwright-summary

  • Postgresql PR Playwright E2E Tests — playwright-summary, playwright-ci-postgresql (chromium-17), playwright-ci-postgresql (chromium-02), playwright-ci-postgresql (chromium-15), playwright-ci-postgresql (chromium-01)

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — manual (2026-08-17T13:15:55Z)

The entry left the queue before it was built, so no checks ran against it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — manual (2026-08-17T16:33:47Z)

No failing check on merge-queue commit 216dee7 — invalidated by an entry ahead in the queue, or a required check timed out.

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-18T06:32:57Z)

Blocked the queue: playwright-summary

Rohit0301 and others added 4 commits August 18, 2026 12:22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

⚠️ UI Checkstyle passed — lint findings in changed files

🔍 ESLint findings in this PR's files — 0 error(s), 25 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), 25 warning(s) across 5 changed file(s).

Count Rule
14 sonarjs/no-duplicate-string
3 openmetadata-imports/no-circular-imports
3 openmetadata-imports/no-lower-layer-page-imports
3 openmetadata-imports/no-impure-pure-utils
1 sonarjs/cyclomatic-complexity
1 sonarjs/cognitive-complexity
All findings
Location Rule Message
🟡 src/components/Explore/ExplorePage.interface.ts:50: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/ExplorePage.interface.ts:51:1 openmetadata-imports/no-lower-layer-page-imports Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here.
🟡 src/components/Explore/ExplorePage.interface.ts:52: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/ExplorePage.interface.ts:53: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:23:1 openmetadata-imports/no-lower-layer-page-imports Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here.
🟡 src/constants/AdvancedSearch.constants.ts:22:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 7 times.
🟡 src/constants/AdvancedSearch.constants.ts:24:19 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 7 times.
🟡 src/constants/AdvancedSearch.constants.ts:27:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 8 times.
🟡 src/constants/AdvancedSearch.constants.ts:34:19 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 7 times.
🟡 src/constants/AdvancedSearch.constants.ts:37:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 5 times.
🟡 src/constants/AdvancedSearch.constants.ts:39:19 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 5 times.
🟡 src/constants/AdvancedSearch.constants.ts:42:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 5 times.
🟡 src/constants/AdvancedSearch.constants.ts:44:19 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 5 times.
🟡 src/constants/AdvancedSearch.constants.ts:47:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 5 times.
🟡 src/constants/AdvancedSearch.constants.ts:97:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 3 times.
🟡 src/constants/AdvancedSearch.constants.ts:102:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 3 times.
🟡 src/constants/AdvancedSearch.constants.ts:107:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/constants/AdvancedSearch.constants.ts:272:12 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 3 times.
🟡 src/constants/AdvancedSearch.constants.ts:274:15 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 3 times.
🟡 src/utils/AdvancedSearchPureUtils.ts:16:1 openmetadata-imports/no-impure-pure-utils Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer.
🟡 src/utils/AdvancedSearchPureUtils.ts:17:1 openmetadata-imports/no-impure-pure-utils Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer.
🟡 src/utils/AdvancedSearchPureUtils.ts:18:1 openmetadata-imports/no-impure-pure-utils Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer.
🟡 src/utils/AdvancedSearchPureUtils.ts:214:23 sonarjs/cyclomatic-complexity {"message":"Function has a complexity of 11 which is greater than 10 authorized.","cost":1,"secondaryLocations":[{"line":214,"column":22,"endLine":214,"endColum
🟡 src/utils/ExploreUtils.tsx:26:1 openmetadata-imports/no-lower-layer-page-imports Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here.
🟡 src/utils/ExploreUtils.tsx:204:4 sonarjs/cognitive-complexity Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

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

make ui-checkstyle-changed

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-19T10:43:37Z)

Blocked the queue: playwright-summary

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-19T22:27:47Z)

Blocked the queue: playwright-summary

@sonarqubecloud

Copy link
Copy Markdown

@gitar-bot

gitar-bot Bot commented Aug 20, 2026

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

Threads sourceFields through the Explore quick filter pipeline to fetch original-case labels via top_hits sub-aggregations, resolving lowercase display issues. No issues found.

✅ 4 resolved
Bug: top_hits array label may not match the aggregation bucket

📄 openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts:210-224 📄 openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts:242-256
For array-typed source paths (domains.displayName, tags.tagFQN, charts.displayName, dataModels.displayName, tasks.displayName), extractSourceValue unconditionally dereferences [0] mid-traversal. But the top_hits document is a single matching asset whose array can contain many entries in arbitrary order, so [0] is not guaranteed to correspond to the current bucket's key. An asset tagged with e.g. Tier.Tier1 and PersonalData.Personal can produce the wrong label for the PersonalData.Personal bucket, showing a label that mismatches the option's (lowercased) key. Consider selecting the array element whose value case-insensitively equals option.key instead of always taking index 0; fall back to the bucket key when no element matches.

Bug: Owner test assumes user.responseData.displayName is defined

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:542 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:560 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:563
UserClass is created via generateRandomUsername(), which only provides firstName/lastName/email/password — no displayName is sent on signup. If the backend does not populate a displayName, user.responseData.displayName is undefined, so ownerName becomes undefined and ownerName.toLowerCase() throws a TypeError, making the test fail (or flaky) rather than validating casing. Note the sibling service test correctly guards with ?? table.serviceResponseData.name, but this owner test does not. Add a fallback (e.g. user.responseData.displayName ?? user.responseData.name) or explicitly patch a displayName on the user before running the assertion.

Bug: Owner test relies on undefined displayName

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:543 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:558 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:561 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts:564
new UserClass() with no args does not populate responseData.displayName (generateRandomUsername only sets email/firstName/lastName/password), so ownerName is undefined. ownerName.toLowerCase() then throws a TypeError and the owner casing test fails. Set an explicit displayName when constructing the user (e.g. pass user data with a displayName) or derive the expected label from a field that is actually populated (firstName/lastName).

Edge Case: toLowerCase() on testid can collide case-differing keys

📄 openmetadata-ui/src/main/resources/ui/src/components/Explore/QuickFilterDropdown.tsx:227
The data-testid is now derived from option.key.toLowerCase() to match the lowercased FQN references in the Playwright specs. If two distinct options have keys differing only in case (e.g. Foo vs foo), they collapse to the same data-testid, which would trigger a Playwright strict-mode/duplicate-selector failure. For aggregation-derived quick filters the keys are already the lowercase ES bucket keys so this is unlikely in practice, but consider keeping the React key (line 225) and the testid consistent or asserting uniqueness if mixed-case option keys are ever possible.

Options

Display: compact → Showing less information.

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

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.

Implement proper casing for Explore quick filters using sourceFields

3 participants