ISSUE #4798-CLT#30157
Conversation
❌ PR checklist incompleteThis 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 |
| @RegisterRowMapper(LatestRecordWithFQNHashMapper.class) | ||
| @SqlQuery( | ||
| "SELECT t1.entityFQNHash, t1.json FROM test_case_resolution_status_time_series t1 " | ||
| + "INNER JOIN (SELECT entityFQNHash, MAX(timestamp) as maxTs " | ||
| + "FROM test_case_resolution_status_time_series WHERE entityFQNHash IN (<entityFQNHashes>) " | ||
| + "GROUP BY entityFQNHash) t2 " | ||
| + "ON t1.entityFQNHash = t2.entityFQNHash AND t1.timestamp = t2.maxTs") | ||
| List<LatestRecordWithFQNHash> getLatestRecordBatchInternal( | ||
| @BindListFQN("entityFQNHashes") List<String> entityFQNs); | ||
|
|
||
| default List<LatestRecordWithFQNHash> getLatestRecordBatch(List<String> entityFQNs) { | ||
| return EntityDAO.queryInChunks(entityFQNs, this::getLatestRecordBatchInternal); | ||
| } |
There was a problem hiding this comment.
💡 Edge Case: Batch latest-record query can return duplicate rows on timestamp ties
getLatestRecordBatchInternal joins on t1.timestamp = t2.maxTs, so if two resolution-status rows for the same entityFQNHash share the exact same MAX(timestamp) (same millisecond), both rows are returned and fetchAndSetIncidentStatuses keeps whichever the map iteration overwrites last. This is non-deterministic and can differ from the single-entity path (getLatestRecord, which orders by timestamp DESC LIMIT 1). Impact is low but consider disambiguating the join (e.g. add a tiebreaker like MAX(id) or select DISTINCT ON) so the inlined status matches the single-get result.
Was this helpful? React with 👍 / 👎
Code Review 👍 Approved with suggestions 0 resolved / 1 findingsEliminates N+1 incident status lookups on the data-quality page by inlining statuses via batched read-time queries. Ensure the 💡 Edge Case: Batch latest-record query can return duplicate rows on timestamp ties📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java:10317-10329 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java:480-494 getLatestRecordBatchInternal joins on 🤖 Prompt for agentsOptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
Describe your changes:
Fixes #4798
I worked on removing the N+1 incident-status lookup on the data-quality / test-case pages because the UI was issuing one
GET /v1/dataQuality/testCases/{stateId}/testCaseIncidentStatusrequest per test case with an open incident. On a table with 50 failing test cases that meant 50 sequential round-trips from the browser after the list had already loaded, leaving the Incident column showing skeletons long after the rest of the table had painted.The fix inlines the incident status into the test case payload behind a new
incidentStatusfield, so a single list request returns everything the table needs.Backend — new
incidentStatusfield onTestCase, resolved via a batched query:testCase.jsongains anincidentStatusfield ($ref→testCaseResolutionStatus.json).TestCaseResolutionStatusTimeSeriesDAO.getLatestRecordBatchfetches the latest resolution status for N test case FQNs in one query (chunked viaEntityDAO.queryInChunks).TestCaseRepository.fetchAndSetIncidentStatuseswires that batch intosetFieldsInBulk(DB-backed list) and into alistFromSearchWithOffsetoverride (ES-backedsearch/list, which builds entities straight from the search doc and therefore never callssetFieldsInBulk).setFieldskeeps a single-entity path forGET /testCases/{id}?fields=incidentStatus.Frontend — consume the inlined field instead of fetching per row:
DataQualityTabreplaces thePromise.allSettledfan-out (fetchTestCaseStatus) with a synchronouscollectInlineIncidentStatusesovertestCase.incidentStatus.TabSpecificField.INCIDENT_STATUSadded to thefieldslist at the four call sites that render the Incident column.Type of change:
High-level design:
Approach. The incident status was already derived server-side per test case; the only problem was that it was reachable solely through a separate per-
stateIdendpoint. Rather than add a new bulk endpoint (which the UI would still have to correlate back to rows), the status is inlined onto the entity behind an opt-infields=incidentStatusflag. Existing clients that don't request the field see no change in payload or query cost.Why two backend wiring points.
setFieldsInBulkcovers the DB-backed list path, butlistFromSearchWithOffsetreconstructs entities from the ES document viaJsonUtils.readOrConvertValueLenientand never routes throughsetFieldsInBulk. Thesearch/listendpoint — which is what all four UI call sites actually hit — therefore needs the explicit override. This mirrors the existingTestSuiteRepository.listFromSearchWithOffsetprecedent.Backward compatibility.
incidentStatusis opt-in viafields; it is absent fromPATCH_FIELDS/UPDATE_FIELDS, so no write path can persist it. No migration needed — the field is derived at read time from the existingtest_case_resolution_status_time_seriestable and is never stored on the entity row.Alternative considered. Denormalizing
incidentStatusonto the entity row / ES document (asincidentIdalready is) would make it free to read, but it would need every incident transition to fan out a test-case reindex, and the status would go stale on any missed event. The read-time batch keeps a single source of truth for one extra query per page.Tests:
Use cases covered
GET /v1/dataQuality/testCases/{id}?fields=incidentStatusreturns the latest incident status for a failed test case.GET /v1/dataQuality/testCases/search/list?fields=incidentStatusinlines the incident status for every returned test case.Unit tests
DataQualityTab.test.tsx— newInline incident statusdescribe block: renders from the inlined field and rebuildstestCaseReference; renders nothing whenincidentStatusis absent. Also drops the now-unusedincidentManagerAPImock.useTestCaseList.test.ts,useTestCaseListPage.test.tsx,TestCases.test.tsx,TableProfilerProvider.test.tsx,TestSuiteDetailsPage.test.tsx— updatedfieldsassertions forincidentStatus.components/Database/Profiler,components/DataQuality,pages/TestSuiteDetailsPage.Backend integration tests
openmetadata-integration-tests/for new/changed API endpoints.TestCaseResourceIT.test_incidentStatusInlinedInGetAndSearchList— assertsincidentStatusis populated withTestCaseResolutionStatusTypes.Newon both the single get and the ES-backedsearch/list.Ingestion integration tests
Checklist:
Fixes <issue-number>: <short explanation>Fixes #<issue-number>above.incidentStatusis derived at read time and never stored; see High-level design.)Greptile Summary
This PR inlines incident status into test-case list responses to remove per-row requests. The main changes are:
incidentStatustest-case field.Confidence Score: 4/5
Status selection and active-incident filtering need fixes before merging.
CollectionDAO.java and DataQualityTab.tsx
Important Files Changed
incidentStatusas a selectable read field.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant UI as Data Quality UI participant API as TestCase search/list participant Repo as TestCaseRepository participant DB as Status time series UI->>API: "GET search/list?fields=incidentStatus" API->>Repo: listFromSearchWithOffset Repo->>DB: Fetch latest statuses by FQN hash DB-->>Repo: Batched status records Repo-->>API: Test cases with incidentStatus API-->>UI: One list response UI->>UI: Match statuses by test-case FQN%%{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"}}}%% sequenceDiagram participant UI as Data Quality UI participant API as TestCase search/list participant Repo as TestCaseRepository participant DB as Status time series UI->>API: "GET search/list?fields=incidentStatus" API->>Repo: listFromSearchWithOffset Repo->>DB: Fetch latest statuses by FQN hash DB-->>Repo: Batched status records Repo-->>API: Test cases with incidentStatus API-->>UI: One list response UI->>UI: Match statuses by test-case FQNComments Outside Diff (2)
openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx, line 352 (link)Resolved Incidents Reappear
The old path fetched a status only when
incidentIdidentified an active incident. The new code accepts any historicalincidentStatus, so a test case whose incident was resolved and whoseincidentIdwas cleared can still showResolvedin the Incident column. Keep the active-incident check when collecting inline statuses.openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java, line 10320-10322 (link)Timestamp Ties Return Arbitrary Status
When two transitions for one test case share the same millisecond timestamp, both rows match the
MAX(timestamp)join. Their order is unspecified, andfetchAndSetIncidentStatusesoverwrites the hash-map entry, so callers can receive either transition as the latest status—for example,Assignedinstead ofResolved. Add a deterministic tie-breaker consistent with the single-record lookup.Reviews (1): Last reviewed commit: "fix: clean failing tests" | Re-trigger Greptile
Context used (3)