Skip to content

feat: show proposed changes with clickable entity links in approval tasks - #29627

Merged
yan-3005 merged 10 commits into
mainfrom
ram/approval-task-proposed-changes-v2
Jul 6, 2026
Merged

feat: show proposed changes with clickable entity links in approval tasks#29627
yan-3005 merged 10 commits into
mainfrom
ram/approval-task-proposed-changes-v2

Conversation

@yan-3005

@yan-3005 yan-3005 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor
image

Summary

Re-implements PR #27201 for the new Task entity system (#25894 Task Redesign).
The proposed-changes preview was originally written into thread.message;
that storage and the legacy CreateApprovalTaskImpl were both removed by
the Task Redesign. This PR ports the feature to the new Task entity's
generic payload field, fixes the merge-across-re-edits behaviour against
the new supersession flow, and fixes a per-field overwrite bug in
buildChangeMap.

Closes the gap left when #27201 was rolled out of main to avoid
conflicts with the Task Redesign.

Fixes #27440
Fixes #29633

Storage

The change-preview map lives in the new Task.payload field under a
namespaced proposedChanges key, so it coexists with other workflow
payload data (RecognizerFeedback's feedback, DAR's expiration metadata,
etc.):

{
  \"proposedChanges\": {
    \"tags\": {\"added\": [\"PII.Sensitive\"], \"removed\": [\"PII.None\"]},
    \"description\": {\"added\": [\"<p>new</p>\"], \"removed\": [\"<p>old</p>\"]}
  }
}

No new schema fields, no migrations.

Identifier extraction priority for each field value:

  1. `tagFQN` — for tag labels
  2. `fullyQualifiedName` — for domains, glossary terms, entity references
  3. `displayName` — for users, teams, entity references
  4. `name` — fallback for named objects
  5. Raw string — for scalar fields like `description`, `entityStatus`

Merge across re-edits (set-cancellation)

Each entity edit spawns a fresh workflow instance which creates a new
Task and asynchronously closes the prior one via
`supersedePriorApprovalTask`. To carry forward the accumulated diff,
`CreateTask` now looks up the prior open approval task for the same
(entity FQN, workflow definition, different workflow instance) before
creating the new Task and uses its payload as the merge base. The
set-cancellation merge produces the net delta from the original entity
state:

mergedAdded   = (oldAdded − newRemoved) ∪ (newAdded − oldRemoved)
mergedRemoved = (oldRemoved − newAdded) ∪ (newRemoved − oldAdded)

Fields whose `added` and `removed` both cancel out are dropped from the
preview.

Example across 3 edits on a glossary term:

Edit Change Accumulated preview
1 tag: `PII.None` → `PII.Sensitive` `tags: {added: [PII.Sensitive], removed: [PII.None]}`
2 add `PersonalData.Personal` `tags: {added: [PII.Sensitive, PersonalData.Personal], removed: [PII.None]}`
3 remove `PII.Sensitive` `tags: {added: [PersonalData.Personal], removed: [PII.None]}`

Per-field accumulator in `buildChangeMap`

For a tag swap (remove `BankNumber`, add `BirthDate`) the
`ChangeDescription` emits a `fieldsAdded` entry and a
`fieldsDeleted` entry both named `tags`. The previous `put`-style
implementation silently overwrote one side, leaving the proposed-changes
card showing only half the diff. The new `accumulate(...)` helper merges
multiple `FieldChange` entries for the same field name through
`FieldDiff.merge`, so both the addition and the removal survive.

Backend changes

`openmetadata-service/.../governance/workflows/util/ChangePreviewUtils.java`

  • `PROPOSED_CHANGES_KEY` constant.
  • `extractProposedChanges(payload)`, `buildProposedChangesPayload(entity, existingPayload)`.
  • `accumulate(...)` helper invoked from `buildChangeMap` across
    fieldsAdded / fieldsDeleted / fieldsUpdated.
  • `[ChangePreview]` diagnostic `LOG.info` line dumping ChangeDescription,
    prior map, new map, and merged result — kept on this PR to make
    production debugging tractable; can be downgraded post-merge.
  • Dead Thread-based `applyChangePreview(...)` removed.

`openmetadata-service/.../governance/workflows/elements/nodes/userTask/CreateTask.java`

  • `findPriorOpenApprovalPayload(...)` looks up the existing open approval
    Task on the same entity bound to a different workflow instance of the
    same workflow definition.
  • `applyProposedChangesIfApproval(taskType, entity, payload)` invoked on
    both the update and create paths for `GlossaryApproval` and
    `RequestApproval` task types.
  • Create-path payload chain: `requestedPayload → workflow payload → prior
    approval payload → null`.

UI changes

`openmetadata-ui/.../components/Entity/Task/TaskTab/TaskTabNew.component.tsx`

  • `extractProposedChanges(task.payload)` parses
    `payload.proposedChanges` into a normalized `Record<field, {added,
    removed}>` map.
  • Renders the Proposed Changes card directly after `taskHeader` when
    `isTaskApprovalRequest` is true and the parsed map is non-empty.
  • `FIELD_ROUTE_MAP` routes `tags` / `tier` / `glossaryTerms` /
    `relatedTerms` / `domains` chips through their canonical
    `RouterUtils` paths.
  • No JSON schema dependency on the frontend — the extractor consumes
    `task.payload` directly.

The chip styles (`.task-proposed-changes-*`) and the
`label.proposed-change-plural` i18n key were already present on `main`
from PR #27201's earlier landing, so no `.less` or locale changes were
required.

Why not use `feedInfo.entitySpecificInfo` (rich card approach)?

The activity-feed formatter pipeline already produces per-field rich card
data via `feedInfo.entitySpecificInfo` — but `entitySpecificInfo` is a
single polymorphic object, not an array. When multiple fields change in
one approval (e.g. tags + description), the formatter returns a
`List` with one entry per field, each carrying its own
`entitySpecificInfo`. There is only one `feedInfo` slot on the task
thread, so only one field's data can be stored — the rest are lost.

Fixing this properly would require turning `feedInfo.entitySpecificInfo`
into an array in the spec, plus a data migration over every existing
Task row. The Task Redesign has just landed (#25894) and we don't want to
follow it immediately with another schema-shaped change. `task.payload`
is already generic `Map<String, Object>` and is the natural slot for
this.

Why not store inside `thread.message` like the original PR?

`CreateApprovalTaskImpl` (Thread-based) was deleted by #25894. Approval
tasks no longer create `Thread` rows — they create new `Task` entities
via `CreateTask`. The original storage location no longer exists.

Test plan

  • Create a glossary term with approval workflow enabled → add one tag
    → open approval task → "Proposed Changes" shows the tag with a green
    chip for added, clickable link to the tag page.
  • Update description AND add a tag in one PATCH → both fields appear
    as separate rows.
  • Re-edit the term while approval is still open → task refreshes with
    merged changes across all edits (exercises set-cancellation across
    supersession).
  • Re-add a previously removed tag → it cancels out from the removed
    list (net zero, disappears from preview).
  • Tag swap in a single PATCH (remove A, add B) — both chips render
    (regression for the `buildChangeMap` overwrite bug).
  • Create a brand new term (no prior version, no `changeDescription`)
    → "Proposed Changes" section is absent.
  • No regressions on non-approval task types (`RequestDescription`,
    `RequestTag`, RecognizerFeedback, DAR).
  • `ChangePreviewUtilsTest` — 39/39 pass.
  • `mvn compile -pl openmetadata-service` — clean.
  • `npx tsc --noEmit` — clean.

Greptile Summary

This PR ports the "Proposed Changes" preview feature for approval tasks from the removed Thread-based storage to the new Task.payload generic field, adapting it to the Task Redesign (#25894). It fixes a per-field overwrite bug in buildChangeMap (tag swap showing only half the diff) and implements set-cancellation merge logic to carry accumulated diffs correctly across re-edits that spawn fresh workflow runs.

  • Backend: ChangePreviewUtils gains buildProposedChangesPayload, extractProposedChanges, and preserveProposedChanges; CreateTask uses findPriorOpenApprovalPayload + preserveProposedChanges on both create and update paths to prevent accumulated proposedChanges from being silently discarded when requestedPayload replaces the task payload.
  • UI: TaskTabNew renders a Proposed Changes card with clickable entity links (tags, glossary terms, domains) after the task header, scoped to isTaskApprovalRequest tasks via useMemo.
  • Tests: 20+ new unit tests in ChangePreviewUtilsTest cover accumulation, JSON round-trips, cancellation-to-empty, incremental change description preference, and preserveProposedChanges edge cases.

Confidence Score: 5/5

Safe to merge — the payload-overwrite issues flagged in prior review rounds are addressed by the new preserveProposedChanges helper, and the per-field overwrite in buildChangeMap is fixed by the accumulate helper.

The two previously flagged payload-accumulation issues (update path and create path) are both resolved cleanly. The buildChangeMap fix is regression-tested by the new accumulates-both-sides test. The incremental-vs-cumulative changeDescription preference is tested and prevents double-counting across re-edits. No schema changes or migrations are needed.

No files require special attention.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/util/ChangePreviewUtils.java Core utility refactored from Thread-based to Task payload storage; adds extractProposedChanges, buildProposedChangesPayload, preserveProposedChanges, and pickIncrementalOrFull; fixes per-field overwrite via accumulate; LOG downgraded to debug with guard.
openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/elements/nodes/userTask/CreateTask.java Adds findPriorOpenApprovalPayload and applyProposedChangesIfApproval; uses preserveProposedChanges on both update and create paths to carry accumulated proposedChanges through requestedPayload substitutions.
openmetadata-service/src/test/java/org/openmetadata/service/governance/workflows/util/ChangePreviewUtilsTest.java Adds 20+ new tests covering accumulate fix, extractProposedChanges, buildProposedChangesPayload, and preserveProposedChanges; covers JSON round-trip, cancellation-to-empty, incremental preference, and key-missing edge cases.
openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTabNew.component.tsx Adds extractProposedChanges parser, FIELD_ROUTE_MAP for clickable entity links, and Proposed Changes card rendered after taskHeader when isTaskApprovalRequest is true and payload has non-empty proposedChanges.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant E as Entity Edit (PATCH)
    participant CT as CreateTask.java
    participant CPR as ChangePreviewUtils
    participant TR as TaskRepository
    participant UI as TaskTabNew (UI)

    E->>CT: delegate task triggered (new workflow run)
    CT->>TR: findExistingTask(requestedTaskId)
    TR-->>CT: "existingTask = null (new task)"
    CT->>TR: listNonTerminalTasksByEntityAndCategory
    TR-->>CT: priorApprovalPayload (accumulated proposedChanges)
    CT->>CPR: preserveProposedChanges(base, priorApprovalPayload)
    CPR-->>CT: payload with prior proposedChanges seeded
    CT->>CPR: applyProposedChangesIfApproval(taskType, entity, payload)
    CPR->>CPR: mergeChangeMaps(priorMap, newMap)
    CPR-->>CT: payload with updated proposedChanges
    CT->>TR: taskRepository.create(task)
    UI->>UI: extractProposedChanges(task.payload)
    UI->>UI: Render Proposed Changes card with clickable entity links
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"}}}%%
sequenceDiagram
    participant E as Entity Edit (PATCH)
    participant CT as CreateTask.java
    participant CPR as ChangePreviewUtils
    participant TR as TaskRepository
    participant UI as TaskTabNew (UI)

    E->>CT: delegate task triggered (new workflow run)
    CT->>TR: findExistingTask(requestedTaskId)
    TR-->>CT: "existingTask = null (new task)"
    CT->>TR: listNonTerminalTasksByEntityAndCategory
    TR-->>CT: priorApprovalPayload (accumulated proposedChanges)
    CT->>CPR: preserveProposedChanges(base, priorApprovalPayload)
    CPR-->>CT: payload with prior proposedChanges seeded
    CT->>CPR: applyProposedChangesIfApproval(taskType, entity, payload)
    CPR->>CPR: mergeChangeMaps(priorMap, newMap)
    CPR-->>CT: payload with updated proposedChanges
    CT->>TR: taskRepository.create(task)
    UI->>UI: extractProposedChanges(task.payload)
    UI->>UI: Render Proposed Changes card with clickable entity links
Loading

Comments Outside Diff (1)

  1. openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/util/ChangePreviewUtils.java, line 161-168 (link)

    P2 parseChangeMap is now dead production code — its only caller, applyChangePreview, was removed in this PR. Only the (pre-existing) tests still call it. Consider removing both the method and the corresponding test section to keep the public API minimal, or leave it with an explicit @Deprecated note if backward compatibility is needed.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  2. openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/elements/nodes/userTask/CreateTask.java, line 500-530 (link)

    P1 Existing task's proposedChanges silently overwritten on the update path

    On the update path (existingTask != null), line 500-501 replaces the full payload with requestedPayload when it is non-null — discarding the accumulated proposedChanges the task already holds. applyProposedChangesIfApproval at line 529-530 then calls extractProposedChanges(requestedPayload), which returns an empty map because requestedPayload carries no proposedChanges key. The merge therefore starts from scratch and only the current edit's delta survives; every prior accumulation on this task instance is lost.

    This is a distinct issue from the create-path problem noted in the previous review: there the concern is that priorApprovalPayload (from a superseded task) is ignored; here the concern is that the current task's own accumulated proposedChanges are discarded whenever requestedPayload is set on an update call. Any workflow definition that injects requestedPayload during a task-update step will silently reset the proposed-changes preview to only the most recent edit's delta.

Reviews (10): Last reviewed commit: "Merge branch 'main' into ram/approval-ta..." | Re-trigger Greptile

…asks

Re-implements PR #27201 for the new Task entity system (#25894 Task Redesign).
The proposed-changes preview was originally written into thread.message; that
storage and the legacy CreateApprovalTaskImpl no longer exist.

## What this adds

Governance approval task panels now show a "Proposed Changes" card listing
exactly which fields changed on the entity (tags, description, synonyms,
owners, etc.) before the approver acts.

Each field renders as a red chip for the removed value and a green chip for
the added value. Tags, glossary terms, related terms, and domains chips are
clickable links to their detail page.

## Storage

The change-preview map is written into the new Task entity's existing
`payload` field under a namespaced `proposedChanges` key, so it coexists
with any future workflow payload data (e.g. RecognizerFeedback's `feedback`
key, DAR's expiration metadata):

```json
{
  "proposedChanges": {
    "tags": {"added": ["PII.Sensitive"], "removed": ["PII.None"]},
    "description": {"added": ["<p>new</p>"], "removed": ["<p>old</p>"]}
  }
}
```

No new schema fields, no migrations.

## Merge across re-edits (set-cancellation)

Each entity edit spawns a fresh workflow instance, which creates a new Task
and asynchronously closes the prior one via supersedePriorApprovalTask. To
carry forward the accumulated diff, CreateTask now looks up the prior open
approval task for the same (entity FQN, workflow definition) BEFORE create
and uses its payload as the merge base. The set-cancellation merge produces
the net delta from the original entity state:

```
mergedAdded   = (oldAdded - newRemoved) ∪ (newAdded - oldRemoved)
mergedRemoved = (oldRemoved - newAdded) ∪ (newRemoved - oldAdded)
```

Fields whose added and removed both cancel out are dropped from the preview.

## Per-field accumulator in buildChangeMap

For a tag swap (remove BankNumber, add BirthDate) the ChangeDescription
emits a fieldsAdded entry AND a fieldsDeleted entry both named `tags`. The
previous put-style implementation silently overwrote one side. The new
accumulate() helper merges multiple FieldChange entries for the same field
name using FieldDiff.merge so both the addition and removal survive.

## Backend changes

- `ChangePreviewUtils.java`
  - `PROPOSED_CHANGES_KEY` constant.
  - `extractProposedChanges(payload)`, `buildProposedChangesPayload(entity, existingPayload)`.
  - `accumulate(...)` helper called from `buildChangeMap` for fieldsAdded /
    fieldsDeleted / fieldsUpdated.
  - `[ChangePreview]` diagnostic log dumping ChangeDescription, prior map,
    new map, and merged result for production debugging.

- `CreateTask.java`
  - `findPriorOpenApprovalPayload(...)` looks up the existing open approval
    task on the same entity bound to a different workflow instance of the
    same workflow definition.
  - `applyProposedChangesIfApproval(taskType, entity, payload)` invoked on
    both the update and create paths for GlossaryApproval and
    RequestApproval task types.
  - Create-path payload chain: requestedPayload → workflow payload → prior
    approval payload → null.

## UI changes

- `TaskTabNew.component.tsx`
  - `extractProposedChanges(task.payload)` parses payload.proposedChanges
    into a normalized `{added, removed}` map.
  - Renders the "Proposed Changes" card directly after taskHeader when
    isTaskApprovalRequest is true and the parsed map is non-empty.
  - `FIELD_ROUTE_MAP` routes tags / tier / glossaryTerms / relatedTerms /
    domains chips through their canonical RouterUtils paths.

## Tests

- `ChangePreviewUtilsTest` adds coverage for `extractProposedChanges`,
  `buildProposedChangesPayload` (fresh, merge, cancel-to-empty), and the
  same-field-in-added-and-deleted accumulator case (39 total tests pass).
@yan-3005
yan-3005 requested a review from a team as a code owner June 30, 2026 14:19
Copilot AI review requested due to automatic review settings June 30, 2026 14:19

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Jun 30, 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

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!

@yan-3005 yan-3005 added the safe to test Add this label to run secure Github workflows on PRs label Jun 30, 2026
- Downgrade [ChangePreview] diagnostic logs from INFO to DEBUG and guard
  with isDebugEnabled(). Avoids logging raw governance content on every
  approval task create/update at INFO level.
- Strip HTML tags from non-route proposed-change chip values (e.g.
  description) so chips show readable plain text instead of literal
  "<p>old</p>" / "<p>new</p>" markup. Route-linked fields (tags, glossary
  terms, domains) keep their raw FQN.
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 63%
63.76% (72730/114056) 46.9% (42306/90190) 48.19% (13015/27007)

…ound-trip

Prior task payload read back from the DB carries `proposedChanges` as a raw
`Map<String, Map<String, List<String>>>` — not a `Map<String, FieldDiff>`.
`extractProposedChanges` was calling `JsonUtils.convertValue` against
`TypeReference<Map<String, FieldDiff>>`; when that throws on Jackson record
materialisation it silently returns an empty map, so each new approval task
starts with no prior context and the cross-edit merge is lost (e.g. an
Article tag added in one edit and removed in the next stays as "added").

Replace `convertValue` with a manual two-shape coercer that accepts either
the in-memory `FieldDiff` record (freshly built) or a raw map (post
round-trip), and add unit tests for both shapes.
Copilot AI review requested due to automatic review settings June 30, 2026 15:02

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…-changes merge

When a glossary term is edited multiple times within an approval session,
each edit creates a new approval task. The prior task's payload already
folds in every in-session change up to that point. If a later edit causes
a version bump (e.g. v0.3 -> v0.4), the entity's cumulative
changeDescription reports the net diff between the two committed versions
- which may double-count fields the prior payload has already cancelled.

Concrete repro from the field:
- v0.3 committed with tag PII.None.
- User added KnowledgeCenter.Article in-session (prior payload: tags
  added=[Article, PII.None]).
- User removed PII.None in-session (prior payload: tags added=[Article];
  PII.None cancelled).
- User removed Article -> v0.3 to v0.4 version bump, entity.changeDescription
  reports tags fieldsDeleted=[PII.None].
- Old behaviour: merge ([Article] - []) U ([] - []) added and
  ([] - []) U ([PII.None] - [Article]) removed -> tags added=[Article],
  removed=[PII.None]. Both chips appear, neither matches the user's net intent.
- Correct behaviour: tag is empty - the term ended this session with no tags,
  same as it started.

Fix: read entity.getIncrementalChangeDescription() (the just-this-hop diff)
when present, falling back to entity.getChangeDescription() only when the
incremental form is empty/null. The incremental form is the right unit of
work to fold into a running merge: it always reflects the single user
action that triggered this CreateTask, never re-asserts already-cancelled
state from earlier hops.

Adds a regression test that fakes the v0.3 -> v0.4 scenario and asserts
the tags entry is dropped from the merged payload.
anuj-kumary
anuj-kumary previously approved these changes Jun 30, 2026
Copilot AI review requested due to automatic review settings July 4, 2026 13:52

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 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +1749 to +1759
{Object.entries(proposedChanges).map(
([field, { added, removed }]) => {
const getUrl = FIELD_ROUTE_MAP[field];

return (
<div
className="task-proposed-changes-field-row"
key={field}>
<Typography.Text className="task-proposed-changes-field-name">
{startCase(field)}
</Typography.Text>
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@yan-3005
yan-3005 merged commit 038a9c6 into main Jul 6, 2026
79 of 81 checks passed
@yan-3005
yan-3005 deleted the ram/approval-task-proposed-changes-v2 branch July 6, 2026 10:48
@gitar-bot

gitar-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 2 resolved / 3 findings

Re-implements the proposed changes preview for approval tasks by storing diffs in the Task payload and enabling cross-edit accumulation. The changes address previously identified accumulation gaps on the create and update paths, though the isTerminalTaskStatus logic requires refinement to correctly handle ManualRevoke status.

⚠️ Bug: isTerminalTaskStatus now treats ManualRevoke as non-terminal

📄 openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/elements/nodes/userTask/CreateTask.java:806-812

The delta refactors isTerminalTaskStatus to delegate to TaskRepository.isTerminalStatus. The comment states the two predicates are equivalent, but they are not: the old inline logic returned true (terminal) for every status except Open, InProgress, Pending, Approved, Granted — so ManualRevoke was treated as TERMINAL. TaskRepository.NON_TERMINAL_STATUSES additionally includes ManualRevoke, so TaskRepository.isTerminalStatus(ManualRevoke) now returns false (non-terminal). This silently changes three workflow-lifecycle guards in CreateTask:

  1. preserveTerminalWorkflowState = isTerminalTaskStatus(currentTask.getStatus()) — a ManualRevoke task was previously preserved (status/stage untouched); it will now have its status and workflow stage overwritten, potentially reviving a revoked task.
  2. The prior-open-approval lookup guard !isTerminalTaskStatus(prior.getStatus()) — a ManualRevoke prior was previously excluded; it will now be picked up as a mergeable/supersedable prior task.
  3. terminateSupersededInstance only terminates when isTerminalTaskStatus(...) is true — a ManualRevoke prior instance will no longer be terminated.

If treating ManualRevoke as non-terminal is intentional, please confirm and update the comment (which currently asserts equivalence). If not, the guards should continue to treat ManualRevoke as terminal to avoid resurrecting revoked tasks.

✅ 2 resolved
Security: Verbose [ChangePreview] INFO logging dumps entity change content

📄 openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/util/ChangePreviewUtils.java:191-205
buildProposedChangesPayload emits an LOG.info on every approval task create/update, including one branch that dumps the full fieldsAdded/fieldsDeleted/fieldsUpdated lists plus the prior/new/merged maps. These contain raw governance content (descriptions, tag FQNs, etc.) that is now written to application logs at INFO on a hot path. This is both log noise and potential exposure of entity content in logs. The PR description itself acknowledges this is temporary ("can be downgraded post-merge"). Recommend downgrading to LOG.debug (and ideally guarding with isDebugEnabled) before merge rather than after.

Quality: Description proposed-change chips render raw HTML as literal text

📄 openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTabNew.component.tsx:1691-1705
For non-route fields such as description, the stored proposed-change values are raw HTML strings (e.g. <p>new</p>, per the PR storage example). The UI renders each value directly as chip text via {val}, and React escapes it, so users see the literal markup <p>new</p> / <p>old</p> rather than readable text. This is safe from XSS (escaped) but is a UX defect introduced by this diff. Consider stripping HTML tags / decoding to plain text before rendering these chips (e.g. a small sanitize-to-text helper), or truncating long HTML values.

🤖 Prompt for agents
Code Review: Re-implements the proposed changes preview for approval tasks by storing diffs in the Task payload and enabling cross-edit accumulation. The changes address previously identified accumulation gaps on the create and update paths, though the `isTerminalTaskStatus` logic requires refinement to correctly handle `ManualRevoke` status.

1. ⚠️ Bug: isTerminalTaskStatus now treats ManualRevoke as non-terminal
   Files: openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/elements/nodes/userTask/CreateTask.java:806-812

   The delta refactors `isTerminalTaskStatus` to delegate to `TaskRepository.isTerminalStatus`. The comment states the two predicates are equivalent, but they are not: the old inline logic returned `true` (terminal) for every status except `Open`, `InProgress`, `Pending`, `Approved`, `Granted` — so `ManualRevoke` was treated as TERMINAL. `TaskRepository.NON_TERMINAL_STATUSES` additionally includes `ManualRevoke`, so `TaskRepository.isTerminalStatus(ManualRevoke)` now returns `false` (non-terminal). This silently changes three workflow-lifecycle guards in CreateTask:
   
   1. `preserveTerminalWorkflowState = isTerminalTaskStatus(currentTask.getStatus())` — a `ManualRevoke` task was previously preserved (status/stage untouched); it will now have its status and workflow stage overwritten, potentially reviving a revoked task.
   2. The prior-open-approval lookup guard `!isTerminalTaskStatus(prior.getStatus())` — a `ManualRevoke` prior was previously excluded; it will now be picked up as a mergeable/supersedable prior task.
   3. `terminateSupersededInstance` only terminates when `isTerminalTaskStatus(...)` is true — a `ManualRevoke` prior instance will no longer be terminated.
   
   If treating `ManualRevoke` as non-terminal is intentional, please confirm and update the comment (which currently asserts equivalence). If not, the guards should continue to treat `ManualRevoke` as terminal to avoid resurrecting revoked tasks.

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

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

Projects

None yet

4 participants