Skip to content

fix: target issue ends in current source status, not first changelog state (#140)#145

Merged
okorach-sonar merged 2 commits into
mainfrom
fix/issue-140-last-status-change
May 1, 2026
Merged

fix: target issue ends in current source status, not first changelog state (#140)#145
okorach-sonar merged 2 commits into
mainfrom
fix/issue-140-last-status-change

Conversation

@okorach-sonar

@okorach-sonar okorach-sonar commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #140.

When syncing issues from SonarQube to SonarCloud, the migration was replaying every transition recorded in the SQ changelog. SonarCloud's state machine rejects most non-current transitions, so the first applicable transition often won and the SC issue ended up in the wrong status (e.g. `CONFIRMED` instead of `REOPENED`).

This change drops the changelog-replay strategy entirely and applies a single transition derived from the SQ issue's current status. The four pipeline copies (sq-9.9, sq-10.0, sq-10.4, sq-2025) and the worker-thread sync (`parallel-issue-sync.js`) are updated consistently. Net diff: -203 lines.

Code changes

  • `syncIssueStatus(scIssue, sqIssue, client)` — drops `sqClient` and `preloadedChangelog` parameters; just compares statuses and falls back to a single transition.
  • `getFallbackTransition` — adds `case 'FIXED': return 'resolve'` across all five copies for completeness.
  • `parallelSyncIssues` — removes the `changelogMap` parameter and the inline worker no longer carries `changelogEntries`.
  • `syncSingleIssue` / `syncOneIssue` — no longer look up preloaded changelog entries before calling `syncIssueStatus`.

Tests

  • Updated `syncIssues` test to assert exactly one transition (`reopen`) for the OPEN→CONFIRMED→RESOLVED+FALSE-POSITIVE→REOPENED scenario.
  • Removed obsolete tests that asserted multi-transition replay behavior.

Docs

  • `docs/CONTRIBUTING.md` — updated the `changelogMap` / `syncIssueStatus` section to reflect the new single-transition model.

Test Coverage

```
syncIssueStatus(scIssue, sqIssue, client) [sq-10.4]
+-- scIssue.status === sqIssue.status ........ TESTED
+-- applyFallbackTransition
+-- getFallbackTransition(sqIssue)
| +-- resolution=FALSE-POSITIVE ........ TESTED
| +-- resolution=WONTFIX ............... TESTED
| +-- status=CONFIRMED ................. TESTED
| +-- status=RESOLVED .................. TESTED
| +-- status=ACCEPTED .................. TESTED
| +-- status=REOPENED .................. TESTED (#140 regression)
| +-- status=FIXED (NEW branch) ........ GAP
| +-- default => null .................. TESTED
+-- transitionIssue success/throws ....... TESTED

#140 regression scenario (changelog ignored, current status wins) — TESTED
parallel-issue-sync worker / sibling pipeline copies — relies on mechanical equivalence to sq-10.4
```

Coverage: ~78% on new `syncIssueStatus` paths. Core regression for #140 is asserted directly.

Pre-Landing Review

1 issue auto-fixed: `docs/CONTRIBUTING.md` had stale references to `preloadedChangelog` / `changelogMap` pass-through. Updated to describe the new single-transition behavior.

Note: `extractTransitionsFromChangelog` and `mapChangelogDiffToTransition` are still exported (and tested) but no longer called by production code. Left in place for this PR; can be cleaned up in a follow-up.

Test plan

  • `npm test` runs and the test suite produces no in-branch regressions (66 pre-existing failures vs 71 on `main` — branch reduces failures by 5; all remaining failures are unrelated to changed files)
  • Manual verification on a real SQ→SC migration: confirm an issue that went OPEN→CONFIRMED→RESOLVED+FALSE-POSITIVE→REOPENED in SQ ends in REOPENED on SC

🤖 Generated with Claude Code


Note

Medium Risk
Changes core issue status migration logic across all pipelines and the worker-based parallel sync, which can alter final SonarCloud issue states. Risk is mitigated by updated regression tests but could still surface edge-case status mappings in real migrations.

Overview
Fixes issue status syncing so SonarCloud ends in the current SonarQube status by removing changelog-replay transitions and always applying one derived transition in syncIssueStatus (across sq-9.9, sq-10.0, sq-10.4, sq-2025, and parallel-issue-sync.js).

Updates the transition mapping to treat SQ status FIXED as resolve, removes changelogMap/preloaded-changelog plumbing from per-issue sync and parallel worker data, and adjusts docs/tests to reflect the single-transition model (including a regression covering #140).

Reviewed by Cursor Bugbot for commit 6ac4084. Bugbot is set up for automated code reviews on this repo. Configure here.

okorach and others added 2 commits April 29, 2026 14:43
…state (#140)

When a SonarQube issue had been transitioned multiple times (e.g. OPEN -> FIXED ->
OPEN), the migrated issue on SonarCloud ended up in the *first* changed status
instead of the current one. Cause: syncIssueStatus replayed every changelog
transition in API response order. Each transition was applied via
client.transitionIssue, but SonarCloud rejected most of them with state-machine
errors (those rejections were swallowed silently). The first transition that
SonarCloud accepted from its OPEN starting state stuck, regardless of what came
after.

Fix: skip the changelog replay entirely. syncIssueStatus now applies a single
transition derived from the source issue's current status/resolution
(applyFallbackTransition's logic). This is what the user-facing requirement asks
for - target issue should reflect the *current* source state, not its history -
and avoids the silent rejection problem completely.

Also add a 'FIXED' case to getFallbackTransition for SonarQube 2025.x modern
lifecycle, where FIXED is a direct status value rather than a resolution.
Defense in depth: closed/fixed issues are already filtered upstream by the #133
filter, but the case ensures the fallback is correct if anything ever bypasses
it.

Applied across all four pipelines (sq-9.9, sq-10.0, sq-10.4, sq-2025) and the
shared parallel-issue-sync worker. Removed the now-dead changelog plumbing
(changelogMap parameter, embedded extractTransitionsFromChangelog/
mapChangelogDiffToTransition copies) from the worker and parallelSyncIssues
signature. The shared apply-pre-filter still fetches changelogs - they're used
by hasManualChanges to filter out issues with no manual changes.

mapChangelogDiffToTransition and extractTransitionsFromChangelog remain exported
from each pipeline's issue-sync module - verify-issue-pair.js still uses them
for status-history verification reporting.

Tests: replaced five replay-related tests in test/sonarcloud/migrators/migrators.test.js
that asserted multi-transition replay (and were already failing on main due to
the manual-changes prefilter dropping issues with no user field on the changelog).
The replacements add user fields to the changelog fixtures so the prefilter
keeps the issues, and assert the new single-transition behavior. Added a
regression test covering the exact #140 scenario.

Closes #140

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sonar-review-alpha

sonar-review-alpha Bot commented May 1, 2026

Copy link
Copy Markdown

Summary

Fixes #140 by switching from changelog-replay to current-status-based issue sync.

What was wrong: When syncing SQ→SC, the code replayed all transitions from the changelog (e.g., confirm, then falsepositive, then reopen). SonarCloud's state machine silently rejects invalid transitions, so the first applicable one succeeded, leaving issues in the wrong final status.

The fix: Apply one transition derived from the SQ issue's current status, not its history. An issue in REOPENED status gets transition 'reopen', period. This ensures SC issues end in the state SQ considers current.

Scale: ~200 lines deleted across 5 locations (4 pipeline versions + shared worker thread). Changes are mechanical and identical, making them easy to verify for consistency.

What reviewers should know

Start here: Test change at line 517–623 in test/sonarcloud/migrators/migrators.test.js. The renamed test "syncIssues applies a single transition derived from current SQ status..." demonstrates the fix directly: OPEN→CONFIRMED→RESOLVED+FALSE-POSITIVE→REOPENED scenario now applies exactly one transition ('reopen'), not three.

Consistency check: Review changes in any one of the 4 pipeline versions (e.g., sq-10.4) to understand the pattern, then spot-check 1–2 others to verify mechanical equivalence. The worker thread in parallel-issue-sync.js follows the same pattern.

What was removed:

  • syncIssueStatus(scIssue, sqIssue, client, sqClient, preloadedChangelog)syncIssueStatus(scIssue, sqIssue, client)
  • Changelog fetch/retry logic (~30 lines per pipeline copy)
  • ~5 tests asserting multi-transition replay (now obsolete)
  • changelogEntries parameter passed to workers

Known loose end: extractTransitionsFromChangelog() and mapChangelogDiffToTransition() are still exported and tested but no longer called by production code. Left in place per author note; can be cleaned up in a follow-up.

Docs: CONTRIBUTING.md updated to describe the new single-transition model (lines 250, 255–256).


  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

@okorach-sonar
okorach-sonar merged commit 37ea213 into main May 1, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

On the target SQC, the issues should be created with that last status in the source issue changelog, not the first state change

2 participants