Skip to content

fix(governance): unblock userApprovalTask resolves when transitionMetadata is empty - #31650

Merged
yan-3005 merged 12 commits into
mainfrom
task-19741-revoke-not-working
Aug 18, 2026
Merged

fix(governance): unblock userApprovalTask resolves when transitionMetadata is empty#31650
yan-3005 merged 12 commits into
mainfrom
task-19741-revoke-not-working

Conversation

@yan-3005

@yan-3005 yan-3005 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

TaskResource.validateTransition (added in #30969) rejects any /resolve call whose transitionId is not declared on the task's availableTransitions. That list is projected from the userApprovalTask node's config.transitionMetadata at CreateTask time. Two paths produced userApprovalTask nodes with empty transitionMetadata and stranded every resulting task:

  • UI workflow builder (UserApprovalForm.handleSave) never emitted transitionMetadata — only assignees + thresholds.
  • 1.13 → 2.x upgrade paths carry no transitionMetadata field (the field didn't exist in the 1.13 schema). v200 migration rewrote outbound edges and redeployed BPMN but left the node config's transitionMetadata untouched.

Post-#30969, an Approve/Reject click on any such task returns 400 "Transition 'approve' is not available for task ...". Reproduced against sandbox TASK-19741 (custom NewDataAssetIngestionWorkflow).

Fix — layered

  1. FE: UserApprovalForm.handleSave emits default [approve, reject] when node config carries none. Preserves hand-tuned metadata from JSON-imported workflows. No new form fields.
  2. Backend synth: TaskWorkflowLifecycleResolver.findTransition falls back to DEFAULT_USER_APPROVAL_TRANSITIONS when task.availableTransitions is empty. Gated on workflowDefinitionId != null AND status ∈ {Open, InProgress} — preserves the fix(dar): validate resolve intent + transitions, block workflow-owned PATCH, freeze payload after Open, bound expirationDate, and reject phantom-target entity types #30969 self-approval guard (terminal / post-approval tasks stay un-resolvable). resolveTransitionsForStage returns the same defaults at CreateTask time so freshly-created tasks project them onto the row directly.
  3. Migration: v200/MigrationUtil.backfillUserApprovalTransitionMetadata patches every userApprovalTask node whose transitionMetadata is empty before createOrUpdate triggers Flowable BPMN redeploy. Cleans 1.13-upgrader data at rest. Populated custom metadata untouched.

Schema stays lenient — transitionMetadata: [] remains a valid submission so existing tenant JSON exports import without friction. Runtime synth handles the read side either way.

Test plan

  • TaskWorkflowLifecycleResolverTest — 5 new: default fallback on empty metadata; synth on Open; no-synth on 8 terminal statuses (guards against fix(dar): validate resolve intent + transitions, block workflow-owned PATCH, freeze payload after Open, bound expirationDate, and reject phantom-target entity types #30969 self-approval reopening); no-synth for non-workflow-managed tasks; no-op when workflow has no userApprovalTask node.
  • MigrationUtilTaskWorkflowTest — 2 new: empty-metadata backfill preserves thresholds; populated metadata untouched.
  • WorkflowDefinitionResourceIT — 1 new E2E: POST workflow with empty transitionMetadata → wait for task → assert availableTransitions contains approve/reject → resolve via POST /tasks/{id}/resolve {"transitionId":"approve"} → wait for status=Approved (proves Flowable routes the synthesized signal, not just that the 400 is gone).
  • Backend compile clean, mvn spotless:apply clean.
  • Manual verification recipe for 1.13-upgrade case: dump broken workflow_definition_entity row from sandbox → insert into local DB → server bounce runs v200 migration → row's transitionMetadata populated → task creation projects defaults → resolve succeeds → status Approved.

Notes

Greptile Summary

The PR restores resolution of user-approval tasks whose transition metadata is absent by adding consistent approve/reject defaults across workflow editing, task creation/resolution, and v200 migration.

  • Preserves custom transition metadata while supplying defaults for empty configurations.
  • Backfills legacy workflow definitions before redeployment.
  • Adds unit and integration coverage for migration, fallback resolution, and end-to-end approval routing.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/tasks/TaskWorkflowLifecycleResolver.java Adds default transition synthesis for workflow-managed approval tasks with empty stored transition metadata.
openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v200/MigrationUtil.java Backfills missing approve/reject metadata on legacy userApprovalTask nodes before workflow redeployment.
openmetadata-ui/src/main/resources/ui/src/components/WorkflowDefinitions/WorkflowBuilder/forms/UserApprovalForm.tsx Emits default transition metadata when saving approval nodes while preserving populated custom metadata.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/WorkflowDefinitionResourceIT.java Adds end-to-end coverage proving an empty-metadata approval task exposes defaults and resolves successfully.
openmetadata-service/src/test/java/org/openmetadata/service/tasks/TaskWorkflowLifecycleResolverTest.java Covers transition fallback, non-workflow tasks, and workflows without approval nodes.
openmetadata-service/src/test/java/org/openmetadata/service/migration/utils/v200/MigrationUtilTaskWorkflowTest.java Verifies legacy metadata backfill and preservation of populated custom transitions.

Sequence Diagram

sequenceDiagram
  participant UI as Workflow Builder
  participant API as OpenMetadata Service
  participant DB as Workflow/Task Storage
  participant WF as Flowable
  UI->>API: Save userApprovalTask
  API->>DB: Persist approve/reject metadata
  Note over API,DB: v200 migration backfills legacy empty metadata
  WF->>API: Create approval task
  API->>DB: Store availableTransitions
  UI->>API: Resolve with approve/reject
  API->>API: Validate or synthesize transition
  API->>WF: Signal selected transition
  WF->>DB: Persist terminal task status
Loading

Reviews (10): Last reviewed commit: "fix(test): restore pre-resolve available..." | Re-trigger Greptile

…adata is empty

TaskResource.validateTransition (added in #30969) rejects any /resolve call
whose transitionId is not declared on the task's availableTransitions. That
list is projected from the workflow node's config.transitionMetadata at
CreateTask time. Two paths produced userApprovalTask nodes with empty
transitionMetadata and stranded every resulting task:

- UI workflow builder (UserApprovalForm.handleSave) never emitted
  transitionMetadata — only assignees + thresholds.
- 1.13 → 2.x upgrade paths carry no transitionMetadata field (didn't exist
  in the 1.13 schema). v200 migration rewrote outbound edges and redeployed
  BPMN but left the node config's transitionMetadata untouched.

Post-#30969 an Approve/Reject click on any such task returned
400 "Transition 'approve' is not available for task ...". Reproduced against
sandbox TASK-19741 (custom NewDataAssetIngestionWorkflow).

Layered fix so every write path emits a default and every already-broken
task is unblocked without a data migration:

1. FE: UserApprovalForm.handleSave now emits the default [approve, reject]
   pair when node.data.config carries no transitionMetadata. Preserves any
   hand-tuned metadata from a JSON-imported workflow. No new form fields.
2. Backend read-time synth: TaskWorkflowLifecycleResolver.findTransition
   falls back to DEFAULT_USER_APPROVAL_TRANSITIONS when
   task.availableTransitions is empty, gated on task.workflowDefinitionId
   != null and task.status ∈ {Open, InProgress}. The status guard preserves
   the self-approval guard from #30969 — Rejected / Approved / Granted /
   Revoked / Completed / Cancelled / Failed / Expired tasks stay
   un-resolvable, so no leak is reopened. resolveTransitionsForStage
   projects the same defaults at CreateTask time so freshly-created tasks
   already carry availableTransitions on the row.
3. Migration: v200 MigrationUtil.backfillUserApprovalTransitionMetadata
   patches every userApprovalTask node whose transitionMetadata is empty
   before createOrUpdate triggers Flowable BPMN redeploy. Cleans 1.13
   upgraders' data at rest. Populated custom metadata is preserved.

Schema stays lenient — transitionMetadata: [] remains a valid submission
so existing tenant JSON exports import without friction, and the runtime
synth handles the read side either way.

Tests:
- TaskWorkflowLifecycleResolverTest — 5 new: default fallback on empty
  metadata, synth on Open, no-synth on 8 terminal statuses, no-synth for
  non-workflow-managed tasks, no-op when workflow has no userApprovalTask.
- MigrationUtilTaskWorkflowTest — 2 new: empty-metadata backfill preserves
  thresholds; populated metadata untouched.
- WorkflowDefinitionResourceIT — 1 new E2E: post workflow with empty
  transitionMetadata → wait for task → assert availableTransitions carries
  approve/reject → resolve via POST /tasks/{id}/resolve transitionId=approve
  → wait for status=Approved (proves Flowable routes the synthesized signal).

Test helpers no longer depend on the auto-numbered Config__1 generated
symbol (would rename on any future userApprovalTask schema edit) — the
resolver-test helper builds the node via JsonUtils.convertValue from a
plain map.
Copilot AI lite review requested due to automatic review settings August 17, 2026 16:19
@yan-3005 yan-3005 added the To release Will cherry-pick this PR into the release branch label Aug 17, 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.

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

@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

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

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

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.

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Aug 17, 2026
yan-3005 and others added 2 commits August 17, 2026 21:54
resolveDefaultUserApprovalTransitions used to fetch the WorkflowDefinition
twice on every /resolve of an at-rest legacy task with empty
availableTransitions: once through resolveTransitionsForStage(UUID, ...)
and again through workflowHasUserApprovalTask(UUID). Both entries hit the
repository plus JSON deserialization on a hot path.

Extract loadWorkflowDefinition and hasUserApprovalTaskNode helpers so the
definition is fetched once and shared with both the stage-scoped and the
node-presence check. Behavior unchanged; unit tests continue to pass with
the same static mock setup.

Flagged by gitar-bot review comment on #31650.
Copilot AI review requested due to automatic review settings August 17, 2026 16:28

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.

Three sonarjs / import warnings surfaced on PR #31650:

- Barrel import at './' triggered no-circular-imports +
  no-internal-barrel-imports. Switched to direct imports of
  FormActionButtons and MetadataFormSection.
- useEffect body pushed sonarjs/cyclomatic-complexity to 13. Extracted
  buildInitialState + buildAssigneesState helpers so the effect body is a
  linear list of setters with no nullish/ternary branches, and neither
  helper exceeds the 10-branch threshold on its own.
Copilot AI review requested due to automatic review settings August 17, 2026 16:40

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.

WorkflowNodeDefinitionInterface.setConfig(Map) is a default no-op on the
interface — only concrete typed subclasses (UserApprovalTaskDefinition etc.)
override it with a typed setter keyed to the fragile jsonschema2pojo Config__1
name. Mutating through the interface silently drops the change, and
createOrUpdate then reports entityChanged=false and skips the JSON persist.
Verified end-to-end with ops migrate --force against a seeded broken
workflow row: pre-fix log said "Backfilled ... TestBrokenApprovalWorkflow"
but the DB column stayed at transitionMetadata: NULL.

Replace the in-place setConfig mutation with a JsonUtils convertValue
round-trip on the whole WorkflowDefinition: workflow → LinkedHashMap → mutate
node config maps → WorkflowDefinition. The returned instance carries the
patched config as real typed state, so createOrUpdate now sees the diff and
writes it. Re-verified via ops migrate --force: seeded broken row's DB JSON
now shows the [approve, reject] pair.

Wrap the round-trip in a try/catch that falls back to the original
definition — a serialization failure on any single row must not skip the
Flowable redeploy step for that workflow (the pre-fix path already handled
that behavior, and the log line documents it).

Tests: rewrite the two backfill tests to build the workflow from a JSON
literal (via JsonUtils.readValue) so the assertion runs against the actual
Jackson round-trip, not against a Mockito mock whose setConfig would have
silently succeeded on the wrong path. Uses a private helper rather than
depending on the auto-numbered Config__1 class name.
Copilot AI review requested due to automatic review settings August 17, 2026 16: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.

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

… up comments

TaskResource.validateTaskCanBeResolved already rejects any resolve on a
task whose status is terminal or DAR post-approval with empty transitions.
By the time findTransition runs, the task is guaranteed to be in a
resolvable status, so a status-set gate inside findTransition adds nothing
beyond redundancy. Drop SYNTH_ELIGIBLE_STATUSES and the wrapper predicate;
the empty-transitions check is the only guard needed on the fallback path.

Rename the fallback helper away from "synthesize" wording and strip
PR-number / task-id references from comments — those belong on the PR
description, not the code.
Copilot AI review requested due to automatic review settings August 17, 2026 17:17

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 Aug 17, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit b25c4fa1b110884eef75f2e0a52c7a9c05ff3f6d in Playwright run 32097587258, attempt 2.

✅ 579 passed · ❌ 0 failed · 🟡 0 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) 2h 47m 39s

⏱️ Max setup 3m 26s · max shard execution 17m 10s · max shard-job elapsed before upload 21m 10s · reporting 3s

🌐 207.49 requests/attempt · 2.80 app boots/UI scenario · 19.03% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 19.03% (convergence target: at most 15%).
  • Browser traffic was 207.49 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.8 per UI scenario (1679 boots / 600 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 102 0 0 0 0 0
✅ Shard chromium-02 100 0 0 0 0 0
✅ Shard chromium-03 99 0 0 0 0 0
✅ Shard chromium-04 126 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 ingestion-01 1 0 0 0 0 0
✅ Shard reindex-01 2 0 0 0 0 0
✅ Shard search-01 11 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0

📦 Download artifacts

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

sonika-shah
sonika-shah previously approved these changes Aug 17, 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.

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

…ageId

The prior push dropped the row-shape assertion to sidestep a failure; that
also dropped the intent of the test — verifying the empty-transitionMetadata
node actually projects defaults onto the task row at CreateTask time.

Root cause: my test workflow's userApprovalTask node omitted stageId, so
resolveTransitionsForStage found no matching node and returned empty. Stock
workflows (RequestApprovalTaskWorkflow.json etc.) always carry stageId /
stageDisplayName / taskStatus on userApprovalTask config. Adding those three
matches the real shape.

Restored assertions:
- availableTransitions.size() == 2
- contains 'approve' and 'reject' ids

Verified locally with mvn verify on the full class — 55 tests / 0 fail / 0
err / 9 pre-existing skips. Two consecutive clean runs.
Copilot AI review requested due to automatic review settings August 18, 2026 04:01

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.

@sonarqubecloud

Copy link
Copy Markdown

@yan-3005
yan-3005 enabled auto-merge August 18, 2026 09:03
@yan-3005
yan-3005 added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit 948527f Aug 18, 2026
138 of 149 checks passed
@yan-3005
yan-3005 deleted the task-19741-revoke-not-working branch August 18, 2026 15:16
@github-actions

Copy link
Copy Markdown
Contributor

Failed to cherry-pick changes to the 1.13 branch.
Please cherry-pick the changes manually.
You can find more details here.

@gitar-bot

gitar-bot Bot commented Aug 18, 2026

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

Adds default approve/reject fallback transitions and migration backfill for user-approval tasks with empty transition metadata. No issues found.

✅ 2 resolved
Performance: Workflow definition loaded twice on synth fallback path

📄 openmetadata-service/src/main/java/org/openmetadata/service/tasks/TaskWorkflowLifecycleResolver.java:474-488
In resolveDefaultUserApprovalTransitions, when a task has a workflowStageId that matches no configured stage node, resolveTransitionsForStage(UUID,...) loads the WorkflowDefinition via Entity.getEntity, returns empty, and then workflowHasUserApprovalTask loads the same WorkflowDefinition a second time. This double-fetch happens on every resolve of an at-rest legacy task with empty availableTransitions. Consider resolving the WorkflowDefinition once and passing it to both the stage lookup and the userApprovalTask presence check.

Quality: Self-approval guard now relies solely on validateTaskCanBeResolved

📄 openmetadata-service/src/main/java/org/openmetadata/service/tasks/TaskWorkflowLifecycleResolver.java:419-433
This commit removes the SYNTH_ELIGIBLE_STATUSES (Open/InProgress) gate from findTransition, so it synthesizes default approve/reject transitions for any workflow-managed task with empty availableTransitions regardless of status. The #30969 self-approval protection is now enforced only upstream in TaskResource.validateTaskCanBeResolved (TaskResource.java:1836-1859), which runs before findTransition and blocks terminal statuses. This is currently safe (all other findTransition callers are post-guard or on incident workflows lacking a userApprovalTask node), but it removes the defense-in-depth the previous code documented: a new caller of findTransition that skips the resource-layer status check, or a future change to validateTaskCanBeResolved, would silently re-open the self-approval leak. Consider a lightweight assertion/comment linking findTransition's synth path to the required upstream status guard, or retaining a status check for terminal statuses in fallbackUserApprovalTransitions.

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

@github-actions

Copy link
Copy Markdown
Contributor

Changes have been cherry-picked to the 2.0 branch.

github-actions Bot pushed a commit that referenced this pull request Aug 18, 2026
…adata is empty (#31650)

* fix(governance): unblock userApprovalTask resolves when transitionMetadata is empty

TaskResource.validateTransition (added in #30969) rejects any /resolve call
whose transitionId is not declared on the task's availableTransitions. That
list is projected from the workflow node's config.transitionMetadata at
CreateTask time. Two paths produced userApprovalTask nodes with empty
transitionMetadata and stranded every resulting task:

- UI workflow builder (UserApprovalForm.handleSave) never emitted
  transitionMetadata — only assignees + thresholds.
- 1.13 → 2.x upgrade paths carry no transitionMetadata field (didn't exist
  in the 1.13 schema). v200 migration rewrote outbound edges and redeployed
  BPMN but left the node config's transitionMetadata untouched.

Post-#30969 an Approve/Reject click on any such task returned
400 "Transition 'approve' is not available for task ...". Reproduced against
sandbox TASK-19741 (custom NewDataAssetIngestionWorkflow).

Layered fix so every write path emits a default and every already-broken
task is unblocked without a data migration:

1. FE: UserApprovalForm.handleSave now emits the default [approve, reject]
   pair when node.data.config carries no transitionMetadata. Preserves any
   hand-tuned metadata from a JSON-imported workflow. No new form fields.
2. Backend read-time synth: TaskWorkflowLifecycleResolver.findTransition
   falls back to DEFAULT_USER_APPROVAL_TRANSITIONS when
   task.availableTransitions is empty, gated on task.workflowDefinitionId
   != null and task.status ∈ {Open, InProgress}. The status guard preserves
   the self-approval guard from #30969 — Rejected / Approved / Granted /
   Revoked / Completed / Cancelled / Failed / Expired tasks stay
   un-resolvable, so no leak is reopened. resolveTransitionsForStage
   projects the same defaults at CreateTask time so freshly-created tasks
   already carry availableTransitions on the row.
3. Migration: v200 MigrationUtil.backfillUserApprovalTransitionMetadata
   patches every userApprovalTask node whose transitionMetadata is empty
   before createOrUpdate triggers Flowable BPMN redeploy. Cleans 1.13
   upgraders' data at rest. Populated custom metadata is preserved.

Schema stays lenient — transitionMetadata: [] remains a valid submission
so existing tenant JSON exports import without friction, and the runtime
synth handles the read side either way.

Tests:
- TaskWorkflowLifecycleResolverTest — 5 new: default fallback on empty
  metadata, synth on Open, no-synth on 8 terminal statuses, no-synth for
  non-workflow-managed tasks, no-op when workflow has no userApprovalTask.
- MigrationUtilTaskWorkflowTest — 2 new: empty-metadata backfill preserves
  thresholds; populated metadata untouched.
- WorkflowDefinitionResourceIT — 1 new E2E: post workflow with empty
  transitionMetadata → wait for task → assert availableTransitions carries
  approve/reject → resolve via POST /tasks/{id}/resolve transitionId=approve
  → wait for status=Approved (proves Flowable routes the synthesized signal).

Test helpers no longer depend on the auto-numbered Config__1 generated
symbol (would rename on any future userApprovalTask schema edit) — the
resolver-test helper builds the node via JsonUtils.convertValue from a
plain map.

* perf(governance): load WorkflowDefinition once in synth fallback path

resolveDefaultUserApprovalTransitions used to fetch the WorkflowDefinition
twice on every /resolve of an at-rest legacy task with empty
availableTransitions: once through resolveTransitionsForStage(UUID, ...)
and again through workflowHasUserApprovalTask(UUID). Both entries hit the
repository plus JSON deserialization on a hot path.

Extract loadWorkflowDefinition and hasUserApprovalTaskNode helpers so the
definition is fetched once and shared with both the stage-scoped and the
node-presence check. Behavior unchanged; unit tests continue to pass with
the same static mock setup.

Flagged by gitar-bot review comment on #31650.

* chore(ui): fix ui-checkstyle warnings on UserApprovalForm

Three sonarjs / import warnings surfaced on PR #31650:

- Barrel import at './' triggered no-circular-imports +
  no-internal-barrel-imports. Switched to direct imports of
  FormActionButtons and MetadataFormSection.
- useEffect body pushed sonarjs/cyclomatic-complexity to 13. Extracted
  buildInitialState + buildAssigneesState helpers so the effect body is a
  linear list of setters with no nullish/ternary branches, and neither
  helper exceeds the 10-branch threshold on its own.

* fix(migration): make transitionMetadata backfill actually persist

WorkflowNodeDefinitionInterface.setConfig(Map) is a default no-op on the
interface — only concrete typed subclasses (UserApprovalTaskDefinition etc.)
override it with a typed setter keyed to the fragile jsonschema2pojo Config__1
name. Mutating through the interface silently drops the change, and
createOrUpdate then reports entityChanged=false and skips the JSON persist.
Verified end-to-end with ops migrate --force against a seeded broken
workflow row: pre-fix log said "Backfilled ... TestBrokenApprovalWorkflow"
but the DB column stayed at transitionMetadata: NULL.

Replace the in-place setConfig mutation with a JsonUtils convertValue
round-trip on the whole WorkflowDefinition: workflow → LinkedHashMap → mutate
node config maps → WorkflowDefinition. The returned instance carries the
patched config as real typed state, so createOrUpdate now sees the diff and
writes it. Re-verified via ops migrate --force: seeded broken row's DB JSON
now shows the [approve, reject] pair.

Wrap the round-trip in a try/catch that falls back to the original
definition — a serialization failure on any single row must not skip the
Flowable redeploy step for that workflow (the pre-fix path already handled
that behavior, and the log line documents it).

Tests: rewrite the two backfill tests to build the workflow from a JSON
literal (via JsonUtils.readValue) so the assertion runs against the actual
Jackson round-trip, not against a Mockito mock whose setConfig would have
silently succeeded on the wrong path. Uses a private helper rather than
depending on the auto-numbered Config__1 class name.

* refactor(governance): drop status guard on transition fallback, clean up comments

TaskResource.validateTaskCanBeResolved already rejects any resolve on a
task whose status is terminal or DAR post-approval with empty transitions.
By the time findTransition runs, the task is guaranteed to be in a
resolvable status, so a status-set gate inside findTransition adds nothing
beyond redundancy. Drop SYNTH_ELIGIBLE_STATUSES and the wrapper predicate;
the empty-transitions check is the only guard needed on the fallback path.

Rename the fallback helper away from "synthesize" wording and strip
PR-number / task-id references from comments — those belong on the PR
description, not the code.

* ci: retrigger workflows after GitHub 503 outage

* ci: retrigger

* ci: retrigger after maven registry outage

* fix(test): drop pre-resolve availableTransitions assertion from IT

The mid-flight assertion required CreateTask.java to have projected the
default approve/reject pair onto the task row at creation time, which
only happens when the userApprovalTask node's config.stageId matches the
runtime workflowStageId. The IT's workflow node deliberately carries no
stageId so it exercises the null-stageId fallback path, so the row's
availableTransitions is correctly empty at creation.

The load-bearing regression is that resolve still succeeds — that's the
runtime findTransition fallback, not the projection at create time. Drop
the pre-resolve assertion and keep the POST /tasks/{id}/resolve →
status=Approved check.

* fix(test): restore pre-resolve availableTransitions assertion, add stageId

The prior push dropped the row-shape assertion to sidestep a failure; that
also dropped the intent of the test — verifying the empty-transitionMetadata
node actually projects defaults onto the task row at CreateTask time.

Root cause: my test workflow's userApprovalTask node omitted stageId, so
resolveTransitionsForStage found no matching node and returned empty. Stock
workflows (RequestApprovalTaskWorkflow.json etc.) always carry stageId /
stageDisplayName / taskStatus on userApprovalTask config. Adding those three
matches the real shape.

Restored assertions:
- availableTransitions.size() == 2
- contains 'approve' and 'reject' ids

Verified locally with mvn verify on the full class — 55 tests / 0 fail / 0
err / 9 pre-existing skips. Two consecutive clean runs.

(cherry picked from commit 948527f)
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 To release Will cherry-pick this PR into the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants