fix(migration): preserve task creator/comments/reactions/resolution and replay incident state on 2.0 upgrade#30213
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 |
…eactions The v200 thread→task migration dropped several fields, leaving migrated tasks with a blank "Created By", no resolver on completed tasks, empty comment history, and no reactions. Map them onto task_entity: - createdBy: set the entityReference (not just createdById) from the legacy thread creator so the task drawer's "Created By" resolves. - resolution.resolvedBy: set from the legacy closedBy; drop the generic "Migrated from thread-based task system" placeholder comment — the real close note now survives as a migrated comment. - comments: migrate the thread's posts[] (replies + resolve/close records) into task comments[] and set commentCount. - reactions: carry thread-level reactions onto the task. Adds buildUserRef and migrateThreadPostsToComments helpers.
…ent tasks The v200 cutover starts every incident (TestCaseResolution) workflow at NewStage and never replays the recorded test_case_resolution_status lifecycle, so an incident that was Ack/Assigned before the upgrade comes back as New / No Owners — and that stale state also makes the "Update Status" resolve transition fail. After backfillOpenTasksToWorkflowInstances starts an incident workflow, replay the test case's latest resolution status onto it, reusing the existing runtime bridge (resolveLegacyTransitionId / buildLegacyResolvedPayload) so migration and runtime stay consistent. Adds TestCaseResolutionStatusRepository .backfillMigratedIncidentTaskStage as the public entry point. Idempotent: no-ops when the task is already at the recorded stage.
e4a11ac to
8fe7d61
Compare
🔴 Playwright Results — 4 pipeline/setup failure(s)✅ 0 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 0 skipped Pipeline and setup failures
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
|
…igrated tasks The migration copied a thread's reactions into taskJson.reactions, but Task has no top-level reactions property (only taskComment does). Every migrated task that had thread reactions then carried an unrecognized field, so the whole task JSON failed to deserialize — GET / resolve / close returned 500 "Failed to process JSON" and the task list came back empty. Remove the top-level reactions write. Per-post reactions still ride along in taskComment.reactions via migrateThreadPostsToComments; thread-level reactions have no 2.0 schema home and are dropped.
…ld-fidelity # Conflicts: # openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v200/MigrationUtil.java
…incident state on 2.0 upgrade Thread->task migration (v200) now carries createdBy, resolution.resolvedBy (from legacy closedBy), and thread posts -> task comments; replays pre-upgrade incident TCRS state (Ack/Assigned + assignee) onto the freshly started workflow instance. TaskResource resolve/close/suggestion-apply now emit X-OpenMetadata-Change: entityUpdated so task-lifecycle alerts fire after upgrade. Complementary to #29978 (relationship restore); this covers field/incident-state fidelity. Internal-branch re-raise of #30213 (fork PRs are blocked by GitHub's new pull_request_target fork-checkout guard).
|
Superseded by #30248 — re-raised from an internal branch because fork PRs are currently blocked by GitHub's new |
Code Review
|
| Compact |
|
Was this helpful? React with 👍 / 👎 | Gitar
…incident state on 2.0 upgrade (#30248) * fix(migration): preserve task creator/comments/resolution and replay incident state on 2.0 upgrade Thread->task migration (v200) now carries createdBy, resolution.resolvedBy (from legacy closedBy), and thread posts -> task comments; replays pre-upgrade incident TCRS state (Ack/Assigned + assignee) onto the freshly started workflow instance. TaskResource resolve/close/suggestion-apply now emit X-OpenMetadata-Change: entityUpdated so task-lifecycle alerts fire after upgrade. Complementary to #29978 (relationship restore); this covers field/incident-state fidelity. Internal-branch re-raise of #30213 (fork PRs are blocked by GitHub's new pull_request_target fork-checkout guard). * fix(migration): make incident-state replay recoverable on migration re-run The replay ran only for freshly-triggered instances and swallowed failures, so a transient failure left the incident permanently at NewStage (the workflowInstanceId guard skipped the task on re-run). Now the replay is attempted for every open incident task and re-attempted on each run (idempotent via backfillMigratedIncidentTaskStage's already-at-stage no-op), and failed replays are tallied and surfaced so operators know to re-run or remediate. * refactor: reframe migration-named repo method as a runtime reconcile primitive Renamed TestCaseResolutionStatusRepository.backfillMigratedIncidentTaskStage -> reconcileIncidentTaskToLatestStatus and de-migration-ified its javadoc. The method wraps the same runtime bridge (applyLegacyStatusToIncidentTask) that storeInternal already uses on live TCRS writes, so it is a general reconcile primitive, not a migration entry point. The migration intent (loop, replay, failed-count) stays in MigrationUtil; no repo-internal method is newly exposed. * refactor: keep migration orchestration out of the repo entirely Removed the migration-only wrapper from TestCaseResolutionStatusRepository. The repo now exposes only the runtime bridge applyLegacyStatusToIncidentTask (already used by storeInternal on live TCRS writes, now public). MigrationUtil.replayMigratedIncidentState does its own orchestration (guards + public getLatestRecord + applyLegacyStatusToIncidentTask), so no migration-purpose method remains in the repo class. * refactor: tidy v200 task-migration helpers for code quality Single trailing return in resolveUserReference (one resolveAdminReference call, no scattered returns) and buildUserRef. Extract buildTaskComment from migrateThreadPostsToComments and buildBackfillWorkflowVariables from startWorkflowInstanceForTask (removes the repeated binding.get().schema() lookups). Behavior unchanged; addresses reviewer feedback on resolveUserReference. * refactor: inline single-use user-ref helpers Removed resolveAdminReference() and userRefToJson() (each called once) by inlining them into resolveUserReference() and buildUserRef(). Promoted ADMIN_USER_NAME to the outer class so the admin fallback uses the constant instead of a literal. Behavior unchanged. * fix(migration): replay incident state by the task's own stateId, not the global latest The v200 migration starts a fresh incident workflow that writes a newer 'New' TCRS record for the same test case, which masks the pre-upgrade Ack/Assigned state when the replay reads getLatestRecord(fqn). Read the migrated task payload's testCaseResolutionStatusId and use getLatestRecordForStateId(stateId) so the replay targets the incident it was migrated from. Skips safely when no stateId is present. * refactor: drop heavy JSON conversions in v200 user-ref/payload helpers Read the incident payload via a direct instanceof Map pattern instead of readOrConvertValue(getPayload(), Map.class), and build the user ref with valueToTree(reference) instead of readTree(pojoToJson(reference)). Same behavior, no intermediate serialization.



Problem
On a
1.12.1 / 1.13 → 2.0upgrade, the thread→task migration preserves the task rows and their type/status mapping, but drops several fields and resets incident state. On migrated (pre-existing) data:resolution.resolvedByis empty and the comment is a generic"Migrated from thread-based task system".task_entity.comments.NewStageand never replays the recordedtest_case_resolution_statuslifecycle, so a pre-upgrade Ack/Assigned incident loses its stage and assignee (and the stale state then makes the "Update Status" resolve fail).New tasks/incidents created in 2.0 are unaffected — this is migration-only.
Fix
v200/MigrationUtil.java— in the thread→task migration:createdBy(entityReference) in addition tocreatedById;resolution.resolvedByfrom the legacyclosedBy(the original close note now survives as a migrated comment, so the placeholder is dropped);posts[]into taskcomments[](+commentCount);reactions[]onto the task.For incidents, after
backfillOpenTasksToWorkflowInstancesstarts the workflow, replay the test case's latest resolution status onto the instance, reusing the existing runtime bridge (resolveLegacyTransitionId/buildLegacyResolvedPayload) so migration and runtime stay consistent. New public entry point:TestCaseResolutionStatusRepository.backfillMigratedIncidentTaskStage. Idempotent — no-ops when the task is already at the recorded stage.Complementary to #29978 (which restores the
ASSIGNED_TO/MENTIONED_INrelationships on Postgres); this PR restores the field- and incident-state fidelity that #29978 does not cover.Testing
openmetadata-servicecompiles; spotless applied.Additional fix: task resolve/close change events (2.0 regression)
Symptom (verified on 1.12.1→2.0, Postgres, in the
change_eventtable): in 1.12.1, resolving/closing a task emitted change events (taskResolved×77,taskClosed×9 underentityType=THREAD), which drove webhook/email/Slack alerts. After the 2.0 task refactor, resolving/closing a migrated task produces zero change events — so task-resolution alerts silently stop working after upgrade.Origin — PR #25894 (Task redesign): the pre-redesign feed endpoints (
/v1/feed/tasks/{id}/resolveand/closeinFeedResource) returned via RestUtil's.toResponse()wrapper, which sets theX-OpenMetadata-Changeheader automatically — soChangeEventHandlerrecorded ataskResolved/taskClosedevent. PR #25894 introducedTaskResourcewith newresolveTask/closeTask/suggestion-applyendpoints that return a bareResponse.ok(task).build()— the header behavior the old feed path got for free via.toResponse()was not carried over, so no change event is recorded (this is a behavior-not-ported gap in the redesign, not a deleted line). Theadd-commentendpoint (header added in #29023) proves the mechanism is intended for tasks; resolve/close were simply missed.How it's recorded:
ChangeEventHandler.getEventTypeFromResponseemits an event only when the response carries theX-OpenMetadata-Changeheader (or is HTTP 201); a bare 200 produces nothing.Fix: set
RestUtil.CHANGE_CUSTOM_HEADER = ENTITY_UPDATEDon the resolve/close/suggestion-apply responses, restoring task lifecycle events (and thus alerting).createalready firesentityCreatedvia the 201 path. Bulk operations return aBulkTaskOperationResult(not an entity) and would need per-item emission — noted, out of scope here.Verification: confirmed at runtime on the same task —
create→entityCreated✓,add-comment→entityUpdated✓, and (pre-fix)resolve/close→nothing; the fix makes resolve/close emitentityUpdated.Greptile Summary
This PR fixes several data-fidelity gaps in the 1.x→2.0 thread-to-task migration and a change-event regression in
TaskResource. The migration now carriescreatedBy,resolvedBy, and threadposts(ascomments) onto each migrated task, and replays pre-upgrade incident TCRS state (Ack/Assigned + assignee) onto freshly created workflow instances. TheTaskResourceresolve/close/suggestion-apply endpoints now emitX-OpenMetadata-Change: entityUpdated, restoring task-lifecycle alerts lost in the PR #25894 redesign.MigrationUtil.java):createdByentity reference andcreatedByIdare now set from the legacy thread's requester;resolution.resolvedByis populated fromclosedBy; threadposts[]are mapped to taskcomments[]with per-post reactions; and theresolveUserReferencehelper is promoted to a shared static method.TestCaseResolutionStatusRepository.java): NewbackfillMigratedIncidentTaskStagemethod replays the latest TCRS record onto a just-started workflow instance via the existingapplyLegacyStatusToIncidentTaskbridge; idempotent by design.TaskResource.java): Adds theX-OpenMetadata-Changeheader toresolveTask,closeTask, andapplySuggestionresponses so task-resolution webhooks/alerts fire correctly after upgrade.Confidence Score: 4/5
Safe to merge with awareness that thread-level reactions are silently dropped during migration and the Flowable workflow timing concern flagged in prior review remains unaddressed.
The migration field-preservation and change-event fixes are correct and well-scoped. The one notable gap is that the PR description claims thread-level reactions are carried over, but the Task schema has no top-level reactions field so they are dropped; only per-post reactions survive via comments. The Flowable async-startup timing issue (replay fires immediately after triggerByKey) was flagged in a prior review round and is still present — if the workflow job executor hasn't advanced to NewStage before resolveTaskWithWorkflow runs, the transition silently fails and the incident stays at New.
MigrationUtil.java — the replayMigratedIncidentState call sequence after workflowHandler.triggerByKey warrants a live migration test to confirm the Flowable instance is in a stable state before the transition fires.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant M as MigrationUtil (v200) participant TR as TaskRepository participant WH as WorkflowHandler participant TCRS as TestCaseResolutionStatusRepository Note over M: Thread-to-Task migration M->>M: buildUserRef(createdByName) → createdBy + createdById M->>M: migrateThreadPostsToComments(threadJson, taskJson) M->>M: buildUserRef(closedBy) → resolution.resolvedBy M->>TR: insertTask(handle, threadId, taskJson) Note over M: backfillOpenTasksToWorkflowInstances M->>TR: listAll(open tasks without workflowInstanceId) loop Each open task M->>WH: triggerByKey(workflowDefinitionFQN, taskId, variables) alt "task.category == Incident" M->>TCRS: backfillMigratedIncidentTaskStage(task) TCRS->>TCRS: getLatestRecord(testCase FQN) TCRS->>TCRS: applyLegacyStatusToIncidentTask(latest, FQN) TCRS->>TR: resolveTaskWithWorkflow(task, transitionId, ...) end end Note over M: TaskResource resolve/close participant TR2 as TaskResource participant CEH as ChangeEventHandler TR2->>TR2: repository.resolveTaskWithWorkflow(...) TR2-->>CEH: Response + X-OpenMetadata-Change: entityUpdated CEH->>CEH: emit taskResolved / taskClosed change event%%{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 M as MigrationUtil (v200) participant TR as TaskRepository participant WH as WorkflowHandler participant TCRS as TestCaseResolutionStatusRepository Note over M: Thread-to-Task migration M->>M: buildUserRef(createdByName) → createdBy + createdById M->>M: migrateThreadPostsToComments(threadJson, taskJson) M->>M: buildUserRef(closedBy) → resolution.resolvedBy M->>TR: insertTask(handle, threadId, taskJson) Note over M: backfillOpenTasksToWorkflowInstances M->>TR: listAll(open tasks without workflowInstanceId) loop Each open task M->>WH: triggerByKey(workflowDefinitionFQN, taskId, variables) alt "task.category == Incident" M->>TCRS: backfillMigratedIncidentTaskStage(task) TCRS->>TCRS: getLatestRecord(testCase FQN) TCRS->>TCRS: applyLegacyStatusToIncidentTask(latest, FQN) TCRS->>TR: resolveTaskWithWorkflow(task, transitionId, ...) end end Note over M: TaskResource resolve/close participant TR2 as TaskResource participant CEH as ChangeEventHandler TR2->>TR2: repository.resolveTaskWithWorkflow(...) TR2-->>CEH: Response + X-OpenMetadata-Change: entityUpdated CEH->>CEH: emit taskResolved / taskClosed change eventComments Outside Diff (1)
openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v200/MigrationUtil.java, line 1959-1970 (link)replayMigratedIncidentStateis called synchronously right afterworkflowHandler.triggerByKey. If the Flowable job executor processes the "start" event asynchronously, the workflow process may not yet have transitioned intoNewStagewhenresolveTaskWithWorkflowruns, causing the transition call to fail. The failure is caught and downgraded to aWARN, so the incident stays atNewwith no visible error — exactly the regression this PR is meant to fix. A brief Flowable-state check or retry before the transition (or moving the replay into anafterCommithook where the workflow is guaranteed to be in a stable state) would close this gap.Reviews (9): Last reviewed commit: "chore: re-trigger CI after merge with ma..." | Re-trigger Greptile