Add changeSummary API endpoint and UI components#26533
Conversation
…ce tracking
Add a new /v1/changeSummary/{entityType}/{id|name/fqn} endpoint that
returns per-field change metadata (who changed it, source type, timestamp).
Supports fieldPrefix filtering for column-level queries on large tables
and limit/offset pagination.
UI additions:
- DescriptionSourceBadge component showing AI badge on AI-generated descriptions
- useChangeSummary hook for fetching change summary data
- changeSummaryAPI REST client
- "accepted-by" translation key
Integration tests added to BaseEntityIT covering all entity types:
get by ID, get by FQN, fieldPrefix filtering, pagination, and 404 cases.
Closes #1648
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The Java checkstyle failed. Please run You can install the pre-commit hooks with |
🟡 Playwright Results — all passed (26 flaky)✅ 3597 passed · ❌ 0 failed · 🟡 26 flaky · ⏭️ 207 skipped
🟡 26 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
- Add authorization checks (VIEW_BASIC) to ChangeSummaryResource endpoints - Fix pagination defaults and simplify pagination logic - Remove silent try-catch in integration tests, use assertThrows for 404 - Wire useChangeSummary hook into GenericProvider context - Render AI-generated badge on entity descriptions (DescriptionV1) - Render AI-generated badge on column descriptions (TableDescription) - Pass changeSummary entry to ColumnDetailPanel's DescriptionSection - Fix stale useMemo dependency in DescriptionV1 header - Fix missing Less variable import in description-source-badge.less - Add Playwright E2E tests for ChangeSummary badge feature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new backend changeSummary read API and wires it into the UI to surface AI/automated description provenance via a badge (plus accompanying tests).
Changes:
- Backend: introduce
GET /v1/changeSummary/{entityType}/{id}and/name/{fqn}endpoints with fieldPrefix filtering and limit/offset pagination. - UI: add
changeSummaryAPI,useChangeSummaryhook, andDescriptionSourceBadgeto display AI/automated provenance on entity + column descriptions. - Tests: add integration tests in
BaseEntityITand a Playwright feature spec for the badge.
Reviewed changes
Copilot reviewed 36 out of 37 changed files in this pull request and generated 34 comments.
Show a summary per file
| File | Description |
|---|---|
| openmetadata-ui/src/main/resources/ui/src/rest/changeSummaryAPI.ts | Adds REST client + types for changeSummary (ID-based). |
| openmetadata-ui/src/main/resources/ui/src/rest/changeSummaryAPI.test.ts | Unit tests for the REST client. |
| openmetadata-ui/src/main/resources/ui/src/hooks/useChangeSummary.ts | New hook to fetch and expose change summary state. |
| openmetadata-ui/src/main/resources/ui/src/components/common/EntityDescription/DescriptionV1.tsx | Renders the new badge next to entity description header. |
| openmetadata-ui/src/main/resources/ui/src/components/common/DescriptionSourceBadge/description-source-badge.less | Styling for the badge/tooltip. |
| openmetadata-ui/src/main/resources/ui/src/components/common/DescriptionSourceBadge/DescriptionSourceBadge.tsx | Badge + tooltip logic based on changeSource metadata. |
| openmetadata-ui/src/main/resources/ui/src/components/common/DescriptionSourceBadge/DescriptionSourceBadge.test.tsx | Unit tests for badge visibility behavior. |
| openmetadata-ui/src/main/resources/ui/src/components/common/DescriptionSourceBadge/DescriptionSourceBadge.interface.ts | Props typing for the badge component. |
| openmetadata-ui/src/main/resources/ui/src/components/common/DescriptionSection/DescriptionSection.tsx | Adds badge to the description section header. |
| openmetadata-ui/src/main/resources/ui/src/components/common/DescriptionSection/DescriptionSection.interface.ts | Adds changeSummaryEntry prop type. |
| openmetadata-ui/src/main/resources/ui/src/components/Database/TableDescription/TableDescription.component.tsx | Shows badge for column descriptions in table description UI. |
| openmetadata-ui/src/main/resources/ui/src/components/Database/ColumnDetailPanel/ColumnDetailPanel.component.tsx | Passes per-column changeSummary entry to DescriptionSection. |
| openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericProvider/GenericProvider.tsx | Fetches changeSummary and provides it via context. |
| openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericProvider/GenericProvider.interface.ts | Adds changeSummary to the generic context type. |
| openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ChangeSummaryBadge.spec.ts | E2E coverage for entity/column badge display. |
| openmetadata-ui/src/main/resources/ui/src/locale/languages/*.json | Adds label.accepted-by translation key across locales. |
| openmetadata-service/src/main/java/org/openmetadata/service/resources/ChangeSummaryResource.java | New backend resource implementing the changeSummary endpoints. |
| openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/BaseEntityIT.java | Adds base integration tests for the new endpoints. |
| .gitignore | Ignores .maestro. |
| if (!showBadge && !actorInfo && !timestampInfo) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
DescriptionSourceBadge can render an empty .description-source-container for changeSource=Manual when callers disable metadata (e.g., showAcceptedBy={false} and showTimestamp={false} as done in headers). In that case config is null, the badge isn’t rendered, and both metadata nodes are null, but the component still returns an empty wrapper because showBadge defaults to true. This leaves a focusable-but-empty element in the DOM and can affect layout. Consider computing hasBadge = showBadge && Boolean(config) and returning null when !hasBadge && !actorInfo && !timestampInfo (and add a unit test for the Manual + metadata-disabled case).
| const tooltipContent = changeSummaryEntry?.changedAt | ||
| ? formatDateTime(changeSummaryEntry.changedAt) | ||
| : undefined; | ||
|
|
||
| const actorLabel = config ? t('label.accepted-by') : t('label.authored-by'); | ||
|
|
||
| const relativeTime = changeSummaryEntry?.changedAt | ||
| ? getShortRelativeTime(changeSummaryEntry.changedAt) || | ||
| formatDate(changeSummaryEntry.changedAt) | ||
| : ''; |
There was a problem hiding this comment.
The tooltip behavior doesn’t match the PR description (“tooltip showing who accepted and when”). For Suggested the tooltip is always the static label.ai-suggested, and for Automated/Propagated the tooltip only shows the timestamp (formatDateTime) and never includes the actor. If the intent is to rely on the metadata row below the description, consider updating the PR description; otherwise, build a tooltip string that includes changedBy + formatted time (and use it especially when showAcceptedBy/showTimestamp are false).
| config.iconOnly ? ( | ||
| <Tooltip title={t(config.tooltipKey)}> | ||
| <span data-testid={config.testId} role="status" tabIndex={0}> | ||
| {config.icon} | ||
| </span> | ||
| </Tooltip> | ||
| ) : ( | ||
| <Tooltip title={tooltipContent}> | ||
| <Tag | ||
| className={classNames( | ||
| 'description-source-badge', | ||
| config.className | ||
| )} | ||
| data-testid={config.testId} | ||
| role="status" | ||
| tabIndex={0}> | ||
| {config.icon} |
There was a problem hiding this comment.
The badge markup uses role="status" on a focusable <span>/<Tag> but provides no aria-label/accessible name. role="status" is intended for live regions, not icons/badges, and screen readers may announce it oddly (or not at all). Consider removing the role, or using an appropriate role (img) plus aria-label (e.g., from t(config.tooltipKey) / t(config.labelKey)) so keyboard/screen-reader users get equivalent context.
|
|
||
| test.describe( | ||
| 'ChangeSummary DescriptionSourceBadge', | ||
| { tag: ['@Features', '@Discovery'] }, |
There was a problem hiding this comment.
We do not have "@Features" tag, and please check usage of "@discovery"
| const entityPatchResponse = await apiContext.patch( | ||
| `/api/v1/tables/name/${table.entityResponseData?.fullyQualifiedName}?changeSource=Suggested`, | ||
| { | ||
| data: [ | ||
| { | ||
| op: 'add', | ||
| path: '/description', | ||
| value: 'AI-generated entity description for badge test', | ||
| }, | ||
| ], | ||
| headers: { | ||
| 'Content-Type': 'application/json-patch+json', | ||
| }, | ||
| } | ||
| ); |
There was a problem hiding this comment.
we have table.patch, can we use that?
| test('AI badge should appear on entity description with Suggested source', async ({ | ||
| page, | ||
| }) => { | ||
| test.slow(); |
There was a problem hiding this comment.
We can remove the test.slow from here, test is very small, it wont take much time
| test('AI badge should appear on column description with Suggested source', async ({ | ||
| page, | ||
| }) => { | ||
| test.slow(); |
There was a problem hiding this comment.
same with here for test.slow, lets check everywhere
| * limitations under the License. | ||
| */ | ||
|
|
||
| import { Tooltip } from 'antd'; |
There was a problem hiding this comment.
it will be great if we use core-component instead of antd for new development
There was a problem hiding this comment.
this will be going in minor release so can't use core-components here
| test.describe( | ||
| 'ChangeSummary DescriptionSourceBadge', | ||
| { tag: [DOMAIN_TAGS.DISCOVERY] }, | ||
| () => { |
There was a problem hiding this comment.
⚠️ Quality: Test entities never cleaned up, causing test data leak
The afterAll block that deleted the main table entity was removed, and three new test cases (automatedTable, propagatedTable, manualTable) each create entities without any cleanup. This accumulates orphaned database services, databases, schemas, and tables in the test environment across runs, which can cause flaky tests and slow down the test suite.
The original test had an afterAll block for cleanup that was deleted in this commit. The new tests create entities in test.step blocks but never call .delete() on them.
Suggested fix:
test.afterAll('Cleanup test entities', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await table.delete(apiContext);
// automatedTable, propagatedTable, manualTable are local
// to each test - consider promoting them to module scope
// so they can be cleaned up here too.
await afterAction();
});
Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion
Code Review
|
| Compact |
|
Was this helpful? React with 👍 / 👎 | Gitar
| // limit=1000 is the backend max. Entities with more tracked field changes | ||
| // will have entries beyond this limit silently omitted. Use fieldPrefix | ||
| // filtering when targeting a specific section (e.g., 'columns.'). | ||
| const { changeSummary } = useChangeSummary( | ||
| isVersionView ? '' : type, | ||
| isVersionView ? '' : data.id ?? '', | ||
| { limit: 1000 } | ||
| ); |
There was a problem hiding this comment.
GenericProvider fetches changeSummary with limit: 1000 for every non-version entity view and no fieldPrefix. This can cause large payloads and still silently truncate changeSummary for entities with >1000 tracked fields (e.g., wide tables), which can lead to missing/incorrect badges. Consider fetching only the needed prefixes (e.g., fieldPrefix=description&limit=1 by default) and loading columns. on demand (e.g., when opening the column panel).
| {config.iconOnly ? ( | ||
| <Tooltip title={t(config.tooltipKey)}> | ||
| <output | ||
| aria-live="polite" | ||
| className="description-source-icon" | ||
| data-testid={config.testId}> | ||
| {config.icon} | ||
| </output> | ||
| </Tooltip> |
There was a problem hiding this comment.
The badge icon is rendered inside an HTML <output> element with aria-live="polite". <output> is intended for form calculation results and aria-live can cause unnecessary screen-reader announcements; a non-semantic container (e.g., span) with an appropriate accessible name (or aria-hidden if decorative) would be more correct.
| const tooltipContent = changeSummaryEntry?.changedAt | ||
| ? formatDateTime(changeSummaryEntry.changedAt) | ||
| : undefined; | ||
|
|
||
| const renderTooltipContent = useMemo(() => { | ||
| if (!showBadge || !config) { | ||
| return ''; | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| {config.iconOnly ? ( | ||
| <Tooltip title={t(config.tooltipKey)}> | ||
| <output | ||
| aria-live="polite" | ||
| className="description-source-icon" | ||
| data-testid={config.testId}> | ||
| {config.icon} | ||
| </output> | ||
| </Tooltip> | ||
| ) : ( | ||
| <Tooltip title={tooltipContent}> | ||
| <div |
There was a problem hiding this comment.
PR description says the AI badge tooltip should show who accepted and when, but the tooltip currently shows only a static label (t(config.tooltipKey)) for icon-only badges and never includes changedBy/changedAt. If the tooltip is intended to carry this metadata, include changedBy/changedAt in the tooltip content (or adjust the PR description / UX accordingly).
|
|



Summary
GET /v1/changeSummary/{entityType}/{id}andGET /v1/changeSummary/{entityType}/name/{fqn}endpoints for retrieving per-field change metadata (who changed it, source type, timestamp)fieldPrefixfiltering for column-level queries on large tables andlimit/offsetpaginationDescriptionSourceBadgeUI component that shows an AI badge on AI-generated descriptions (Suggested/Automated sources), with tooltip showing who accepted and whenuseChangeSummaryReact hook andchangeSummaryAPIREST clientBaseEntityITcovering all entity types: get by ID, get by FQN, fieldPrefix filtering, pagination, and 404 casesTest plan
ChangeSummaryResource.javacompiles cleanTableResourceITandTopicResourceIT(5 changeSummary tests each via BaseEntityIT)Closes #26547
🤖 Generated with Claude Code