Skip to content

Fixes #30526: mirror incident re-assignment into the TCRS timeline - #30525

Open
manerow wants to merge 3 commits into
mainfrom
fix/incident-reassign-tcrs-mirror
Open

Fixes #30526: mirror incident re-assignment into the TCRS timeline#30525
manerow wants to merge 3 commits into
mainfrom
fix/incident-reassign-tcrs-mirror

Conversation

@manerow

@manerow manerow commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Describe your changes:

Fixes #30526

Re-assigning an open incident returned 200 and updated the Task, but every UI surface kept showing the previous assignee.

Task is the source of truth for incidents; IncidentTcrsSyncHandler mirrors it into the TCRS time series for the legacy consumers (DQ page badge, search aggregations, dashboards, exporters). Both the test case page header and the Incident Manager list write to the Task and then read the assignee back out of TCRS, so a mirror that misses the change is a mirror that shows a stale assignee.

reassign is a self-loop: TestCaseResolutionTaskWorkflow.json targets stage assigned and the edge is AssignedStage -> AssignedStage, so workflowStageId is identical before and after. The handler had two guards, and neither looked at the assignee:

  • the pre-image guard returned early whenever the stage was unchanged, so nothing was written;
  • the idempotency guard compared only the status type, so Assigned -> Assigned would have been skipped anyway.

The result was that testCaseResolutionStatusDetails.assignee stayed frozen at the first assignee for the life of the incident, on /testCaseIncidentStatus/stateId/{id}, ?latest=true, /search/list and the inline testCase.incidentStatus.

What changed

The mirrored projection of a task is its status plus, in the assigned stage, its assignee. That is now the only thing the handler keys off:

  • handleTaskCreate and handleTaskUpdate collapse into one sync(Task), called from both postCreate and postUpdate. The stage pre-image guard is gone: it was a performance filter that had quietly become the correctness filter, which is how a same-stage transition slipped through.
  • The remaining guard compares status and assignee id against the latest stored record, so a re-assignment appends and everything else still skips.

Appending rather than patching matches cleanUpAssignees, which already treats an assignee change as a new record, and matches the pre-task-first behaviour the mirror exists to preserve. Severity survives because syncFromTask inherits it from the previous record for the stateId, and setResolutionMetrics is unaffected since it only writes on New -> * and on Resolved.

The assignee id is compared rather than the whole details object because a stored EntityReference comes back hydrated with extra fields; a deep compare would report a difference on every unrelated task update and append duplicates.

No UI change is needed: the write happens inside the resolve request, so the existing read-back picks it up.

Cost: getLatestRecordForStateId now runs on every incident-task update instead of only on stage changes. Incident tasks change at human rate, and a repeated failure on an open incident does not touch the task, so this is a few small indexed reads per incident.

Type of change:

  • Bug fix

High-level design:

N/A — small change.

Tests:

Use cases covered

  • Re-assigning an incident already in the assigned stage updates the assignee shown on the test case page header and in the Incident Manager list.
  • Each assignment appends its own record, so the incident timeline keeps the reassignment history.
  • Comments and unrelated patches on an incident task still write nothing.
  • Commenting on a resolved incident does not duplicate the Resolved record.

Backend integration tests

  • openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IncidentTaskIntegrationIT.java
    • testReassignment_AssignedToAssigned strengthened: it asserted the Task assignee and the TCRS status type, but the type was already Assigned from the first assign and the helper used anyMatch, so it passed without a record ever being written. It now asserts the latest TCRS record's assignee and the full assignment timeline.
    • testUpdatesThatDoNotChangeTheMirror_AppendNothing added, covering the no-op update and duplicate-Resolved cases.
    • assertTcrsStatusEventually deleted. It asserted anyMatch(status) over the whole timeline, which is what made the reassign test vacuous, and it is blind to the same class of bug elsewhere: in testResolveAfterReopen_CompletesSameTask the timeline is New -> Resolved -> Ack -> Resolved, so anyMatch(Resolved) passes on the first record even if the second is never mirrored. All 9 remaining call sites now assert the latest record and, where the incident is assigned, its assignee.
    • assertLatestTcrsEventually / tcrsTimeline helpers added.

Manual testing performed

Local stack on this branch:

  1. Created a test case, posted a failed result to open an incident.
  2. POST /v1/tasks/{id}/resolve {"transitionId":"assign"} to user A, confirmed Assigned / A.
  3. POST /v1/tasks/{id}/resolve {"transitionId":"reassign"} to user B.
  4. Confirmed the Task and all four TCRS read paths now report B, and that exactly one record was appended.

Before the change, step 4 reported A on every TCRS path.

UI screen recording / screenshots:

Not applicable.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR is linked to a GitHub issue via Fixes above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests (unit / integration / Playwright as applicable) and listed them above.
  • I have added a test that covers the exact scenario we are fixing.

Greptile Summary

The PR updates incident-to-TCRS synchronization and its integration coverage.

  • Mirrors both incident status and assigned-user identity.
  • Runs projection comparison for every persisted incident-task update.
  • Adds latest-record, reassignment-history, and no-op-update assertions.

Confidence Score: 3/5

The PR is not yet safe to merge because concurrent incident updates can leave legacy consumers observing a stale final TCRS projection.

The previously reported race remains: post-commit synchronization independently reads the latest TCRS record and later inserts a new one without per-state serialization or an atomic compare-and-append operation, so concurrent task updates can persist projections in the wrong final order.

Files Needing Attention: openmetadata-service/src/main/java/org/openmetadata/service/events/lifecycle/handlers/IncidentTcrsSyncHandler.java; openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseResolutionStatusRepository.java

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/events/lifecycle/handlers/IncidentTcrsSyncHandler.java Replaces stage-transition filtering with status-and-assignee projection comparison.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java Invokes the unified incident projection synchronizer after Task creation and updates.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IncidentTaskIntegrationIT.java Strengthens latest-record assertions and covers reassignment history and projection-preserving updates.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Task as TaskRepository
  participant Sync as IncidentTcrsSyncHandler
  participant TCRS as TCRS Repository
  Client->>Task: Update incident task
  Task->>Task: Commit canonical task state
  Task->>Sync: sync(updated)
  Sync->>TCRS: Read latest projection
  Sync->>Sync: Compare status and assignee ID
  alt Projection changed
    Sync->>TCRS: Append projected record
  else Projection unchanged
    Sync-->>Task: Skip append
  end
Loading

Reviews (3): Last reviewed commit: "test(dq): use SequencedCollection getLas..." | Re-trigger Greptile

@manerow
manerow requested a review from a team as a code owner July 27, 2026 12:44
@github-actions

github-actions Bot commented Jul 27, 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 github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Jul 27, 2026
@manerow manerow changed the title Fixes open-metadata/openmetadata-collate#3908: mirror incident re-assignment into the TCRS timeline Fixes #30526: mirror incident re-assignment into the TCRS timeline Jul 27, 2026
@gitar-bot

gitar-bot Bot commented Jul 27, 2026

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

Refactors TCRS timeline tests to use SequencedCollection getLast(), addressing the collection access finding. Code is clean and well-tested.

✅ 1 resolved
Quality: Use SequencedCollection getLast() instead of get(size()-1)

📄 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IncidentTaskIntegrationIT.java:737 📄 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IncidentTaskIntegrationIT.java:726
The new helper reads the newest TCRS record with timeline.get(timeline.size() - 1). The project's Java 21 conventions require List.getLast() on SequencedCollections instead of index arithmetic. Replace with timeline.getLast() for the already-sorted list.

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit c6d91d7f405d645748789761f4a0d830b85238f4 in Playwright run 30268402418, attempt 1.

✅ 537 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 5 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) 46m 49s

⏱️ Max setup 2m 57s · max shard execution 18m 20s · max shard-job elapsed before upload 22m 8s · reporting 3s

🌐 202.77 requests/attempt · 2.86 app boots/UI scenario · 5.27% common-shard skew

Optimization targets still in progress:

  • Browser traffic was 202.77 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.86 per UI scenario (1596 boots / 558 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 143 0 0 3 0 0
✅ Shard chromium-02 133 0 0 0 0 0
✅ Shard chromium-03 123 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 23 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 10 0 0 0 0 0
✅ Shard search-rbac-01 27 0 0 2 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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incident re-assignment is not mirrored into the TestCaseResolutionStatus timeline

1 participant