Skip to content

perf: tighten issue sync pre-filter — drop hasAssignee, add updateDate gate (Issue #158)#159

Open
okorach-sonar wants to merge 2 commits into
mainfrom
perf/issue-158-issue-sync-prefilter
Open

perf: tighten issue sync pre-filter — drop hasAssignee, add updateDate gate (Issue #158)#159
okorach-sonar wants to merge 2 commits into
mainfrom
perf/issue-158-issue-sync-prefilter

Conversation

@okorach-sonar

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

Copy link
Copy Markdown
Collaborator

Summary

Tighten the hasManualChanges pre-filter that decides whether a source issue enters the matched-pair sync set. Two independent reductions of the candidate set, both targeted at Issue #158:

  1. Drop hasAssignee (5e7da4b). A source-side assignee alone is no longer a sync trigger — assignees don't map onto SonarQube Cloud's user base, so an issue with only an assignee and no other signal would just be a no-op (or a mapped-away skip) at sync time.

  2. Add an updateDate gate (7d6818d). The pre-filter now requires updateDate !== creationDate before checking human changelog / non-migration comments / custom tags. A brand-new issue from analysis has matching dates and is now skipped regardless of any other signal — there is nothing manual to propagate.

Old semantics:

qualifies if ANY of (humanChangelog | nonMigrationComments | customTags |
                     hasAssignee | updateDate≠creationDate)

New semantics:

gate on (updateDate ≠ creationDate);
then qualify on ANY of (humanChangelog | nonMigrationComments | customTags)

On a reference project (sonar-tools, ~1200 source issues, ~200 actually require sync), the issue/hotspot sync currently dominates total migration time at ~88%. Tightening the candidate set this way is expected to bring the work close to the actual ~200 and cut sync time accordingly.

Files changed (3)

  • src/shared/utils/issue-sync/has-manual-changes.js — new gate semantics, hasAssignee removed, JSDoc updated.
  • test/utils/issue-sync.test.js — existing assertions get explicit creationDate/updateDate where the issue should pass the gate; assignee tests rewritten to assert the new behaviour; new test verifies the gate blocks even strong signals when dates match.
  • test/sonarcloud/migrators/migrators.test.js#140 regression-test fixture gains explicit dates so it still passes the gate.

Test plan

  • All 42 unit tests in test/utils/issue-sync.test.js pass.
  • Full suite: 70 pre-existing failures on main, 70 with this branch. Sorted-diff confirms identical failure set — zero regressions.
  • Live regression on sonar-tools: confirm sync wall-clock time drops from ~2:30 toward something close to the lower bound implied by ~200 actual sync candidates.

🤖 Generated with Claude Code

@sonar-review-alpha

sonar-review-alpha Bot commented May 8, 2026

Copy link
Copy Markdown

Summary

Tightens the issue sync pre-filter in two ways:

  1. Drops assignee as a sync signal: Issues with only a source-side assignee (and no other manual changes) are no longer kept. This is intentional—source assignees don't map to SonarCloud's user base, so they're not actionable.

  2. Makes updateDate ≠ creationDate a gate, not a fallback: This changes from "sync if any signal is present, OR if dates differ" to "first check if dates differ; if they match, reject regardless of other signals." The gate must pass before checking changelog, comments, or tags. This catches brand-new issues that analysis just created and haven't been touched since.

On the reference project (sonar-tools, ~1200 issues, ~200 needing sync), this should cut issue sync time from ~2:30 to near the lower bound implied by actual candidates.

What reviewers should know

Start here: Read the hasManualChanges() function in src/shared/utils/issue-sync/has-manual-changes.js—the logic change is small but the semantics are important. The updated JSDoc explains the gate logic clearly.

Key behavioral changes for reviewers:

  • The gate (updateDate ≠ creationDate) is now enforced first and unconditionally. Even if an issue has a human changelog, comments, and tags, it will still be rejected if its dates match (lines 24–26 in the new code).
  • Assignment alone no longer qualifies an issue, and the hasAssignee() helper is removed entirely.
  • The test file shows this well: look at the "gate blocks" tests (lines 209–221) which pass multiple signals but still expect false because dates match.

Testing coverage:

  • The assignee tests have been rewritten (4 tests changed, 1 new): they now verify assignee-only rejection and assignee + other signals acceptance.
  • Dates-gate tests are comprehensive, testing missing dates and dates-matching with various signal combinations (lines 207–242).
  • A helper withDates() (lines 11–16) scaffolds issues for tests, reducing noise and making it clear which tests require date setup.
  • The performance benchmark test uses the helper and now creates all 10,000 test issues with dates (lines 375–378).

Watch for: The gate check is strict. If you're reviewing performance impact, note that this will filter more aggressively on projects where many issues have creationDate == updateDate (e.g., fresh scans with few manual edits). This is intentional—the author expects it to improve sync time significantly.


  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

sonar-review-alpha[bot]

This comment was marked as outdated.

Comment thread src/commands/migrate/helpers/build-migrate-perf-config.js
* @returns {boolean}
*/
export function hasManualChanges(issue, changelog) {
if (!wasUpdatedAfterCreation(issue)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description claims safety-net behavior that code removes

Medium Severity

The updateDate != creationDate check was changed from a positive signal (catch-all safety net that qualified an issue on its own) to a mandatory gate. Issues where updateDate differs from creationDate but whose changes aren't detected by the three explicit checks (human changelog, manual comments, custom tags) will now be silently dropped. This could miss issues modified through admin tools, batch operations, or changelog entries with empty user fields.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6ffecbc. Configure here.

The hasManualChanges pre-filter was keeping every source issue that had an
assignee, even when nothing else about the issue had been touched manually.
Source assignees do not map onto the target SonarQube Cloud user base, so an
assignee on the source side is not a reason to keep an issue in the sync
candidate set — once filtered through, it would just be a no-op (or a
mapped-away skip) at sync time.

On a reference project (sonar-tools, ~1200 issues, ~200 actually requiring
sync), removing this check is expected to drop the candidate set
dramatically and cut sync time, which currently dominates total migration
time at ~88%.

Other criteria are preserved:
- Human-authored changelog entries
- Non-migration comments
- Custom tags
- updateDate ≠ creationDate (catch-all safety net)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@sonar-review-alpha sonar-review-alpha Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two tests in the sqClient section of migrators.test.js have been silently broken by the gate change. They still pass, but only because the gate filters the test fixture issue before the scenario under test ever executes — both assert stats.transitioned === 0, which is now trivially true for the wrong reason. The author fixed the first test in that section (correctly adding dates to its fixture) but missed the other two.

🗣️ Give feedback

creationDate: '2024-01-01T00:00:00+0000', updateDate: '2024-06-15T10:30:00+0000' }
];

const stats = await syncIssues('proj', sqIssues, client, { concurrency: 1, sqClient });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix here is correct, but two tests that follow immediately also pass sqClient and have the same problem — their sqIssues fixtures have no creationDate/updateDate:

  • syncIssues with sqClient returns not transitioned when transition fails (≈line 2269): the fixture issue at line 2282 has no dates. The gate filters it before the transition is attempted, so transitionIssue is never called and stats.transitioned === 0 passes vacuously. The error-handling path is no longer exercised.
  • syncIssues skips transition when SC and SQ statuses already match (≈line 2290): same story — the fixture at line 2302 has no dates, the issue is filtered, and transitionIssue.callCount === 0 passes for the wrong reason.

Both fixtures need the same creationDate/updateDate treatment as the issue at line 2258–2259.

  • Mark as noise

Comment on lines 26 to 28
if (hasHumanChangelog(changelog)) return true;
if (hasManualComments(issue)) return true;
if (hasCustomTags(issue)) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving wasUpdatedAfterCreation to a gate (first check, hard-return false) changes the contract beyond what the PR description's "What's preserved" section implies. Previously an issue with a human-authored changelog entry would qualify regardless of whether updateDate was present or equal to creationDate. Now, missing or equal dates block every other signal.

In practice a real SQ issue with changelog entries will always have updateDate != creationDate, so the risk is low. But the edge case is worth calling out: if the SQ API ever returns an issue with genuine manual signals but without date fields (data quality issue, partial API response), it will be silently skipped. The old code erred on the side of syncing; the new code errs on the side of filtering.

  • Mark as noise

The hasManualChanges pre-filter that decides whether a source issue is a
sync candidate now requires the gate `updateDate !== creationDate` BEFORE
checking human changelog entries, non-migration comments, or custom tags.
A brand-new issue that just came out of analysis has matching dates and
is now skipped regardless of any other signal — eliminating stragglers
where there is nothing real to propagate.

Old semantics: an issue qualified if ANY of (changelog | comments | tags
| updateDate-changed) was true.

New semantics: gate on updateDate-changed; if it passes, qualify on
(changelog | comments | tags). The catch-all `updateDate !=
creationDate` alone no longer keeps an issue in the candidate set —
combined with dropping the assignee check (the prior commit), this
brings the pre-filter close to "issues that actually have something the
user touched and that maps onto SonarQube Cloud".

Tests updated to match: existing assertions get explicit
creationDate/updateDate where the issue should pass the gate, and a new
case verifies the gate blocks even strong signals when the dates match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@okorach-sonar
okorach-sonar force-pushed the perf/issue-158-issue-sync-prefilter branch from 6ffecbc to 7d6818d Compare May 8, 2026 12:21

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7d6818d. Configure here.

* @returns {boolean}
*/
export function hasManualChanges(issue, changelog) {
if (!wasUpdatedAfterCreation(issue)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelogs fetched for issues that immediately fail gate

Low Severity

fetchSqChangelogs is called for ALL issues before the hasManualChanges filter is applied. Since the new date gate in hasManualChanges only needs issue.updateDate and issue.creationDate (already present on issue objects), issues failing the gate could be excluded before the expensive batch changelog fetch, avoiding unnecessary API calls for every issue where dates match or are missing.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d6818d. Configure here.

* @returns {boolean}
*/
export function hasManualChanges(issue, changelog) {
if (!wasUpdatedAfterCreation(issue)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignee-only issues silently skip assignment sync

Medium Severity

The downstream syncSingleIssue calls syncIssueAssignment, which uses userMappings to actively sync assignees to the target platform. Removing hasAssignee from the pre-filter means issues whose only manual change is an assignment (e.g., auto-assigned at creation or assigned via API with an empty changelog user) will now be filtered out before reaching syncIssueAssignment. This silently breaks the userMappings feature for those issues, contradicting the PR's claim that "source assignees don't map onto the target platform's user base" — the codebase clearly supports mapped assignment syncing.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d6818d. Configure here.

@sonar-review-alpha sonar-review-alpha Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both previously flagged issues remain open — this PR does not address them.

🗣️ Give feedback

@okorach-sonar okorach-sonar changed the title perf: drop hasAssignee from issue sync pre-filter (Issue #158) perf: tighten issue sync pre-filter — drop hasAssignee, add updateDate gate (Issue #158) May 9, 2026
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.

2 participants