Skip to content

fix(column-bulk): sort column grid results alphabetically by column name#29699

Merged
sonika-shah merged 7 commits into
mainfrom
sonika/fix-column-grid-sort
Jul 3, 2026
Merged

fix(column-bulk): sort column grid results alphabetically by column name#29699
sonika-shah merged 7 commits into
mainfrom
sonika/fix-column-grid-sort

Conversation

@sonika-shah

@sonika-shah sonika-shah commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #29698

Problem

In Column Bulk Operations, the Column Name list is not in a consistent order — e.g. c_abbrevation / c_abnorm appear below c_adm* entries instead of at the top. The order is neither reliably alphabetical nor otherwise predictable, which makes the list hard to scan.

Root cause

The aggregators request ascending order from the search engine (composite aggregation order(SortOrder.Asc) on columns.name.keyword; the pattern-search path builds a sorted TreeSet), but that order is lost before the grid is built: the parse step collects buckets into a HashMap, and ColumnMetadataGrouper.groupColumns iterates entrySet() and emits rows in map (hash-bucket) order without re-sorting. So the grid renders in effectively arbitrary order. This affects both the default browse and the pattern-search paths, on both OpenSearch and Elasticsearch.

Fix

Sort the output gridItems by columnName (ascending, case-insensitive) before returning from ColumnMetadataGrouper.groupColumns. Both engines and all three query paths (browse / pattern / tag) funnel through this method, so the single change makes ordering deterministic everywhere. Case-insensitive ordering matches the pattern path's existing TreeSet(String.CASE_INSENSITIVE_ORDER) dedup and is friendlier for scanning.

Pagination is unaffected: browse pages arrive in global ascending order and pattern pages are sliced from a globally-sorted name list, so sorting each returned page alphabetically matches the global order.

Tests

ColumnMetadataGrouperTest:

  • testGroupColumns_resultsAreSortedAscendingByColumnName — feeds a scrambled HashMap (mirroring the reported c_ab* below c_adm* case) and asserts ascending output.
  • testGroupColumns_sortIsCaseInsensitiveZebra, apple, Mangoapple, Mango, Zebra.

Verified: all query paths and pagination stay sorted

1. Every return path funnels through groupColumns — filters only narrow the query, they don't add a path. The three paths and their sinks:

Request Path Sorted sink
tags/glossary (± search, ± filters) tag path groupColumns (OS OpenSearchColumnAggregator.java:219, ES ElasticSearchColumnAggregator.java:298)
search pattern only (± filters) pattern path groupColumns (OS :175, ES :261)
default / any scope-filter combo (service, database, schema, domain, serviceType, entityType, metadataStatus) browse path groupColumns (OS :117, ES :179)

Dispatch is in aggregateColumns (OS :101/:106, ES :118/:123); everything else falls through to browse.

2. All three paths paginate with the same (case-insensitive) collation as the sort, so page boundaries don't drift:

  • Pattern path collects names in TreeSet(String.CASE_INSENSITIVE_ORDER) before slicing (OS :156, ES :202).
  • Tag path uses TreeMap(String.CASE_INSENSITIVE_ORDER) (OS :235, ES :314).
  • Browse path paginates via a composite aggregation on *.name.keyword, and that keyword sub-field carries lowercase_normalizer on all five grid asset indexes, so the composite orders case-insensitively:
    • elasticsearch/en/table_index_mapping.jsoncolumns.name.fields.keyword
    • dashboard_data_model_index_mapping.jsoncolumns.name.fields.keyword
    • topic_index_mapping.jsonmessageSchema.schemaFields.name.fields.keyword
    • search_entity_index_mapping.jsonfields.name.fields.keyword
    • container_index_mapping.jsondataModel.columns.name.fields.keyword
    • (field paths matched in ElasticSearchColumnAggregator.java:70-79)

Displayed column names keep their original case (reconstructed from _source); only ordering uses the normalized term. Result: the grid is deterministically ascending-alphabetical for the default view, any single or combined filter, search, and tags — consistently across pagination on both OpenSearch and Elasticsearch.

Greptile Summary

Sorts the column bulk operations grid alphabetically (case-insensitive) by fixing the single shared output point — ColumnMetadataGrouper.groupColumns — rather than patching each of the three query paths individually.

  • ColumnMetadataGrouper.java: Adds a gridItems.sort(...) call after the list is built, using String.CASE_INSENSITIVE_ORDER as the primary key and natural String ordering as a tie-breaker for names that differ only in case (e.g. \"Name\" / \"name\"). The comment explains why the input order is unreliable (HashMap iteration).
  • ColumnMetadataGrouperTest.java: Adds three focused tests covering the reported scrambled-c_ab*/c_adm* scenario, case-insensitive ordering, and deterministic tie-breaking for same-case-fold names; also extracts a shared singleOccurrence helper.

Confidence Score: 5/5

Safe to merge — the change is a single in-place sort at the shared output point, with no side-effects on upstream aggregation or pagination logic.

The fix is minimal and well-contained: one sort call is inserted at the only place all query paths converge, so there is no risk of missed paths or double-sorting. The comparator correctly handles case-insensitive ordering with a stable tie-breaker, and three targeted tests directly exercise the reported bug scenario, the case-folding behavior, and the determinism of same-fold names. No data-model or API contract changes are involved.

No files require special attention.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/search/ColumnMetadataGrouper.java Adds a stable, case-insensitive sort of gridItems before return; change is minimal, correctly placed at the single sink for all three query paths.
openmetadata-service/src/test/java/org/openmetadata/service/search/ColumnMetadataGrouperTest.java Three new tests cover the reported ordering bug, case-insensitive sort, and same-fold tie-breaking; all assertions are correct and use LinkedHashMap to guarantee a deterministic scrambled input.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[aggregateColumns] -->|tag filter present| B[Tag path]
    A -->|search pattern only| C[Pattern path]
    A -->|default / scope filter| D[Browse path]

    B -->|HashMap of columnName → occurrences| E[groupColumns]
    C -->|TreeSet-deduped names, HashMap results| E
    D -->|composite-agg buckets, HashMap| E

    E --> F["Build gridItems list (HashMap iteration order)"]
    F --> G["gridItems.sort(CASE_INSENSITIVE_ORDER + tie-breaker)"]
    G --> H[Return sorted ColumnGridItem list]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[aggregateColumns] -->|tag filter present| B[Tag path]
    A -->|search pattern only| C[Pattern path]
    A -->|default / scope filter| D[Browse path]

    B -->|HashMap of columnName → occurrences| E[groupColumns]
    C -->|TreeSet-deduped names, HashMap results| E
    D -->|composite-agg buckets, HashMap| E

    E --> F["Build gridItems list (HashMap iteration order)"]
    F --> G["gridItems.sort(CASE_INSENSITIVE_ORDER + tie-breaker)"]
    G --> H[Return sorted ColumnGridItem list]
Loading

Reviews (7): Last reviewed commit: "Merge branch 'main' into sonika/fix-colu..." | Re-trigger Greptile

The column grid rendered rows in HashMap iteration order because
ColumnMetadataGrouper.groupColumns emitted them in entrySet order without
sorting, discarding the ascending order the aggregators request from the
search engine. Sort gridItems by columnName (case-insensitive) so the grid
is deterministic and alphabetical across both engines and all query paths.

Fixes #29698
Copilot AI review requested due to automatic review settings July 2, 2026 10:52
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

✅ PR checks passed

The linked issue has a description and all required Shipping project fields set. Thanks!

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Jul 2, 2026

Copilot AI 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.

Pull request overview

This PR fixes non-deterministic ordering in Column Bulk Operations by enforcing a consistent alphabetical sort of column grid results (case-insensitive) at the final grouping stage, so UI lists are predictable regardless of map/aggregation iteration order.

Changes:

  • Sort ColumnMetadataGrouper.groupColumns output by columnName (case-insensitive) before returning results.
  • Add unit tests asserting ascending, case-insensitive ordering of grouped column grid items.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
openmetadata-service/src/main/java/org/openmetadata/service/search/ColumnMetadataGrouper.java Sorts grouped ColumnGridItem results by column name to make grid ordering deterministic.
openmetadata-service/src/test/java/org/openmetadata/service/search/ColumnMetadataGrouperTest.java Adds tests validating sorted output from groupColumns, including case-insensitive ordering.

Address review feedback on the column grid sort:
- Add a case-sensitive tie-breaker (.thenComparing) so column names differing
  only in case have a stable order instead of falling back to map iteration.
- Use LinkedHashMap with deliberately non-sorted insertion in the sort tests so
  the unsorted input is deterministic (not reliant on HashMap iteration order).
- Add a test asserting case-only collisions sort deterministically.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (27 flaky)

✅ 4033 passed · ❌ 0 failed · 🟡 27 flaky · ⏭️ 22 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 2 801 0 9 8
✅ Shard 3 809 0 0 7
🟡 Shard 4 807 0 4 5
🟡 Shard 5 852 0 5 0
🟡 Shard 6 764 0 9 2
🟡 27 flaky test(s) (passed on retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/BulkEditOperationBadges.spec.ts › Glossary bulk edit search filters rows and clear restores them (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Database service (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Database (shard 2, 2 retries)
  • Features/BulkImport.spec.ts › Database Schema (shard 2, 2 retries)
  • Features/ContextCenterPermission.spec.ts › user with editAll permission can see restore action but not delete action on an archived document, and can restore it (shard 2, 1 retry)
  • Features/DataQuality/DataQuality.spec.ts › TestCase filters (shard 2, 1 retry)
  • Features/EntitySummaryPanel.spec.ts › should cancel edit display name modal (shard 2, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.ts › should move term with children to different glossary (shard 2, 1 retry)
  • Flow/PersonaDeletionUserProfile.spec.ts › User profile loads correctly before and after persona deletion (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Set & Update all CP types on apiCollection (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Set & Update all CP types on dataProduct (shard 4, 1 retry)
  • Pages/DataContractsSemanticRules.spec.ts › Validate Owner Rule Is_Not (shard 4, 1 retry)
  • Pages/Domains.spec.ts › preview names affected data products when moving across domains (shard 5, 1 retry)
  • Pages/Entity.spec.ts › Delete Container (shard 5, 1 retry)
  • Pages/ExploreBrowse.spec.ts › service type drill-down disables unrelated roots and query-panel Clear resets it (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should verify deleted user not visible in owner selection for table (shard 5, 1 retry)
  • Features/AutoPilot.spec.ts › Create Service and check the AutoPilot status (shard 6, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should allow Data Steward to edit glossary terms for searchIndex (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Check for Circular Reference in Glossary Import (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Glossary CSV import preserves typed relations (shard 6, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Glossary CSV import rejects unknown relation type (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify Impact Analysis service filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for pipelineService in platform lineage (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for apiService in platform lineage (shard 6, 1 retry)
  • Pages/TasksUIFlow.spec.ts › Create and reject tag task for Pipeline via UI (shard 6, 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

Copilot AI review requested due to automatic review settings July 3, 2026 03:24

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings July 3, 2026 04:03

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

@sonika-shah sonika-shah force-pushed the sonika/fix-column-grid-sort branch from 828fb2f to 22c669b Compare July 3, 2026 04:10
Copilot AI review requested due to automatic review settings July 3, 2026 04:10

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@sonika-shah sonika-shah enabled auto-merge (squash) July 3, 2026 04:50
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

@sonika-shah sonika-shah merged commit bd1cef7 into main Jul 3, 2026
71 of 72 checks passed
@sonika-shah sonika-shah deleted the sonika/fix-column-grid-sort branch July 3, 2026 08:53
@gitar-bot

gitar-bot Bot commented Jul 3, 2026

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

Implements case-insensitive alphabetical sorting in ColumnMetadataGrouper.groupColumns to ensure deterministic column grid ordering across all query paths. This resolves the non-deterministic display order of column names.

✅ 1 resolved
Edge Case: Case-only name collisions have non-deterministic order

📄 openmetadata-service/src/main/java/org/openmetadata/service/search/ColumnMetadataGrouper.java:108-109
The new sort uses Comparator.comparing(ColumnGridItem::getColumnName, String.CASE_INSENSITIVE_ORDER). CASE_INSENSITIVE_ORDER treats names that differ only in case (e.g. Apple vs apple) as equal, so their relative order in the grid falls back to the source HashMap's undefined iteration order — the exact non-determinism this PR aims to eliminate. If such collisions are possible in the data, consider a tie-breaker, e.g. Comparator.comparing(ColumnGridItem::getColumnName, String.CASE_INSENSITIVE_ORDER).thenComparing(ColumnGridItem::getColumnName). Low severity since case-only duplicate column names are uncommon.

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 👍 / 👎 | Gitar

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

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Column Name List Is Not Displayed in a Consistent Sort Order

3 participants