Skip to content

Fix SonarQube open issues metric pulling - #4295

Open
imykhno wants to merge 1 commit into
redhat-developer:mainfrom
imykhno:fix/scorecard-sonarqube-open-issues-metric
Open

Fix SonarQube open issues metric pulling#4295
imykhno wants to merge 1 commit into
redhat-developer:mainfrom
imykhno:fix/scorecard-sonarqube-open-issues-metric

Conversation

@imykhno

@imykhno imykhno commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Hey, I just made a Pull Request!

This PR includes a fix for the SonarQube Open Issues scorecard card, which is displayed when the SonarQube scorecard module is installed. The issue was identified while testing the module on a private project with restricted access.
Below is how the scorecard page looked before the fix (note the SonarQube Open Issues card):

Screenshot 2026-08-13 at 16 23 52

Below is how the scorecard page looks after the fix:

Screenshot 2026-08-13 at 16 42 50

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

@rhdh-gh-app

rhdh-gh-app Bot commented Aug 13, 2026

Copy link
Copy Markdown

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-sonarqube workspaces/scorecard/plugins/scorecard-backend-module-sonarqube patch v1.0.2

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 13, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:38 PM UTC · Completed 2:54 PM UTC

Commit: f1a9166 · View workflow run →

@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

Fix SonarQube open-issues metric for inaccessible projects

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Validate SonarQube project accessibility before querying open issues.
• Prefer paging.total (fallback to legacy total) for open-issues counts.
• Add regression tests to prevent reporting 0 when the project is inaccessible.
Diagram

graph TD
  SC["Scorecard backend"] --> CL["SonarQubeClient"] --> SH["/api/components/show"] --> DEC{Accessible?} --> IS["/api/issues/search"] --> RES["openIssues count"]
  DEC -- "no" --> ERR["Propagate API error"]

  subgraph Legend
    direction LR
    _svc["[ ] Service"] ~~~ _api["[ ] API endpoint"] ~~~ _dec{"{ } Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Rely only on issues search and special-case permission errors
  • ➕ One less network call per metric computation
  • ➕ Simpler happy-path flow
  • ➖ Permission/inaccessibility can manifest differently across SonarQube versions/instances
  • ➖ Harder to ensure the UI never shows a misleading 0 vs an actual 0
2. Return a sentinel (e.g., null/NaN) for inaccessible projects
  • ➕ Allows UI to render explicit “N/A” state instead of error/number
  • ➕ Avoids conflating real 0 with inaccessible project
  • ➖ Requires broader API/typing changes across metric interfaces and consumers
  • ➖ Potentially breaking change for existing scorecard consumers

Recommendation: The chosen approach (explicit accessibility check via components/show, then issues search, and returning paging.total ?? total ?? 0) is a good fit for a patch fix: it prevents false-zero reporting without widening the contract surface. The extra call is acceptable given the correctness improvement and clearer error propagation when access is restricted.

Files changed (3) +107 / -7

Bug fix (1) +9 / -1
SonarQubeClient.tsVerify project access before fetching open issues +9/-1

Verify project access before fetching open issues

• Adds a preliminary '/api/components/show' request to ensure the project is accessible before querying issues. Adjusts the returned count to prefer 'paging.total', with fallbacks to 'total' and then '0'.

workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts

Tests (1) +93 / -6
SonarQubeClient.test.tsExpand tests for access-check + open issues count behavior +93/-6

Expand tests for access-check + open issues count behavior

• Updates 'getOpenIssuesCount' tests to validate the new access-check call, error propagation, and correct count selection. Adds coverage for zero-issues case and preferring 'paging.total' over the legacy 'total' field.

workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.test.ts

Other (1) +5 / -0
six-seas-wear.mdAdd patch changeset for SonarQube open-issues fix +5/-0

Add patch changeset for SonarQube open-issues fix

• Introduces a changeset declaring a patch release for the SonarQube scorecard backend module and documents the user-visible issue being fixed.

workspaces/scorecard/.changeset/six-seas-wear.md

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Extra access-check request 🐞 Bug ➹ Performance
Description
getOpenIssuesCount now performs an unconditional /api/components/show call before every
/api/issues/search, doubling SonarQube API traffic and adding latency for each entity’s
sonarqube.openIssues computation. This increases pull duration and the chance of transient
failures, especially on larger catalogs or frequent schedules.
Code

workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts[R112-115]

+    // Additional check to ensure the project is accessible
+    await this.fetchApi(
+      `/api/components/show?component=${encodeURIComponent(projectKey)}`,
+      instanceName,
Relevance

●● Moderate

Perf tradeoff vs intended access-fix; no clear historical precedent rejecting extra verification
calls.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR adds an unconditional access-check fetch, and the metric provider calls getOpenIssuesCount
per entity when calculating sonarqube.openIssues, multiplying the added network call across the
catalog.

workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts[106-123]
workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/metricProviders/SonarQubeNumberMetricProvider.ts[44-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getOpenIssuesCount()` always calls `/api/components/show` before `/api/issues/search`, doubling the network calls for this metric.

### Issue Context
The open-issues metric is computed per entity (catalog scale), so even small per-entity overhead multiplies quickly.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts[110-123]
- workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.test.ts[85-187]

### Suggested fix
Preserve the "inaccessible project should not show 0" behavior while reducing calls:
- Option A (simple): Call `/api/issues/search` first; if the returned total is > 0, return it immediately; only if it is 0 then call `/api/components/show` to distinguish "no issues" from "inaccessible".
- Option B: Cache successful access checks (e.g., in-memory Map keyed by `{instanceName, projectKey}`) for the duration of a pull run / short TTL.
- Update tests to reflect the new request ordering/conditional behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Silent zero fallback ✓ Resolved 🐞 Bug ≡ Correctness
Description
getOpenIssuesCount now returns 0 when the issues-search response is missing both paging.total
and total, which can mask malformed/unsupported API responses as “no issues”. This can incorrectly
record a successful metric result instead of surfacing an error, reintroducing misleading zeros in
new failure modes.
Code

workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts[R123-126]

    );
-    return data.total;
+
+    return data.paging?.total ?? data.total ?? 0;
  }
Relevance

● Weak

Team previously rejected throwing/guarding on missing SonarQube fields; prefers non-failing
behavior.

PR-#2576

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new ?? 0 causes a missing-count response to be treated as 0, which then flows through normal
threshold evaluation and gets stored as a successful metric value rather than an error.

workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts[106-126]
workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/metricProviders/SonarQubeConfig.ts[222-232]
workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[160-233]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`SonarQubeClient.getOpenIssuesCount()` falls back to `0` when the response JSON lacks both `paging.total` and `total`. That makes an unexpected response shape look like a legitimate "0 open issues" result.

### Issue Context
The scorecard pipeline treats returned numbers as real metric values and evaluates thresholds; returning 0 will often be classified as a success for `sonarqube.openIssues`.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts[118-126]

### Suggested fix
- Compute `const total = data?.paging?.total ?? data?.total;`
- If `typeof total !== 'number'` (optionally also allow numeric strings), throw a clear error like:
 - `Unexpected SonarQube issues/search response: missing paging.total/total for <url>`
- Return the validated/coerced numeric value.
- Update/extend unit tests to cover the "missing totals" shape and assert that it throws (rather than returning 0).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 11 rules
✅ Cross-repo context
  Explored: repo: redhat-developer/rhdh (sha: 88d4ad1d)
  Not relevant to this PR: redhat-developer/rhdh-chart
  Not relevant to this PR: redhat-developer/rhdh-operator
  Not relevant to this PR: redhat-developer/rhdh-local

Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.84%. Comparing base (2e4c46e) to head (0580eca).
⚠️ Report is 22 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4295      +/-   ##
==========================================
+ Coverage   59.58%   59.84%   +0.25%     
==========================================
  Files        2459     2491      +32     
  Lines       98272    99060     +788     
  Branches    27448    27573     +125     
==========================================
+ Hits        58559    59279     +720     
- Misses      39348    39416      +68     
  Partials      365      365              
Flag Coverage Δ *Carryforward flag
adoption-insights 84.55% <ø> (ø) Carriedforward from 2e4c46e
ai-integrations 68.29% <ø> (ø) Carriedforward from 2e4c46e
app-defaults 69.79% <ø> (ø) Carriedforward from 2e4c46e
augment 46.67% <ø> (ø) Carriedforward from 2e4c46e
boost 77.63% <ø> (ø) Carriedforward from 2e4c46e
bulk-import 72.79% <ø> (ø) Carriedforward from 2e4c46e
cost-management 13.55% <ø> (ø) Carriedforward from 2e4c46e
dcm 67.21% <ø> (ø) Carriedforward from 2e4c46e
e2e-adoption-insights 60.00% <ø> (ø) Carriedforward from 2e4c46e
e2e-extensions 62.13% <ø> (ø) Carriedforward from 2e4c46e
e2e-intelligent-assistant 46.74% <ø> (ø) Carriedforward from 2e4c46e
extensions 56.59% <ø> (ø) Carriedforward from 2e4c46e
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 2e4c46e
global-header 66.50% <ø> (ø) Carriedforward from 2e4c46e
homepage 47.50% <ø> (ø) Carriedforward from 2e4c46e
install-dynamic-plugins 59.95% <ø> (ø) Carriedforward from 2e4c46e
intelligent-assistant 75.42% <ø> (ø) Carriedforward from 2e4c46e
konflux 91.98% <ø> (ø) Carriedforward from 2e4c46e
lightspeed 69.02% <ø> (ø) Carriedforward from 2e4c46e
mcp-integrations 83.40% <ø> (ø) Carriedforward from 2e4c46e
orchestrator 71.27% <ø> (ø) Carriedforward from 2e4c46e
quickstart 63.74% <ø> (ø) Carriedforward from 2e4c46e
sandbox 79.56% <ø> (ø) Carriedforward from 2e4c46e
scorecard 87.09% <100.00%> (+0.84%) ⬆️
theme 88.14% <ø> (ø) Carriedforward from 2e4c46e
translations 5.12% <ø> (ø) Carriedforward from 2e4c46e
x2a 79.20% <ø> (ø) Carriedforward from 2e4c46e

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2e4c46e...0580eca. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [incomplete-fix-scope] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts — The pre-flight accessibility check is applied only to getOpenIssuesCount(). The other methods (getQualityGateStatus, getMeasures) likely don't need it — they read structured response fields (data.projectStatus.status, data.component.measures) that would throw TypeError on an empty/unexpected response, failing loud rather than returning misleading data like /api/issues/search does with {total: 0}. Worth verifying as a follow-up.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts:113 — The pre-flight check adds an extra HTTP round-trip per getOpenIssuesCount call. The sequential approach is the correct trade-off for scorecard metric polling.

  • [missing-authorization] — No linked GitHub issue. The PR body and changeset clearly describe the bug and the fix is well-scoped, so authorization is implicitly established. Consider linking an issue for traceability.

  • [naming-conventions] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.test.ts:24 — Migrating from ConfigReader to mockServices.rootConfig in this file creates a minor inconsistency with sibling test files in the same plugin that still use ConfigReader.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.test.ts — The ConfigReadermockServices.rootConfig migration is tangential to the bug fix but is a reasonable test infrastructure improvement.

Previous run

Review

Findings

Medium

  • [error-handling-idiom] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts:128 — The fallback chain data.paging?.total ?? data.total ?? 0 silently returns 0 when neither paging.total nor total is present in the response. This masks a malformed API response. The established pattern in sibling methods (getQualityGateStatus, getMeasures) is to access response fields directly without fallback, letting the caller see the failure via a thrown error. Silently returning 0 for a malformed response is the same class of problem this PR aims to fix.
    Remediation: Remove the ?? 0 terminal fallback. The expression data.paging?.total ?? data.total is sufficient to support both API shapes and will surface (rather than silently absorb) a malformed response.

  • [api-shape] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts:115 — The new pre-flight fetchApi call to /api/components/show doubles HTTP requests for getOpenIssuesCount and is a pattern not used by any other method in this class. The SonarQube API quirk motivating this change (issues/search returns total: 0 for inaccessible projects instead of an error) is not documented in the code. Without this documentation, future maintainers may question or remove the extra call.
    Remediation: Add a code comment or JSDoc @remarks explaining that /api/issues/search returns a misleading success (200 with total: 0) for inaccessible projects, which is why the pre-flight check is necessary.

Low

  • [test-adequacy] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.test.ts — No test covers the legacy SonarQube API response format where paging is absent and only the top-level total field is present. All current tests include a paging object in the mock response. A test with a response like { total: 42 } (no paging) would document and lock in the backward-compatible fallback behavior.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts:128 — The PR bundles two related but logically distinct fixes: (1) the pre-flight access check via /api/components/show, and (2) preferring data.paging?.total over data.total. The PR description and changeset mention only the access/visibility problem. Consider updating the description to explain why the paging.total change is also needed.

  • [design-smell] workspaces/scorecard/plugins/scorecard-backend-module-sonarqube/src/clients/SonarQubeClient.ts:113 — The pre-flight access check is added only to getOpenIssuesCount, not to sibling methods getQualityGateStatus or getMeasures. If the SonarQube API quirk is specific to the issues/search endpoint, a brief comment noting why sibling methods don't need the same guard would prevent future maintenance confusion.


Labels: PR fixes a bug in the SonarQube scorecard backend module

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 13, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:54 AM UTC · Ended 9:57 AM UTC

Commit: d6a87ae · View workflow run →

@imykhno
imykhno force-pushed the fix/scorecard-sonarqube-open-issues-metric branch from d6a87ae to 2d2ac64 Compare August 14, 2026 09:57
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:58 AM UTC · Ended 9:59 AM UTC

Commit: 2d2ac64 · View workflow run →

Signed-off-by: Ihor Mykhno imykhno@redhat.com

Assisted-By: Cursor <cursoragent@cursor.com>
@imykhno
imykhno force-pushed the fix/scorecard-sonarqube-open-issues-metric branch from 2d2ac64 to 0580eca Compare August 14, 2026 09:59
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 10:00 AM UTC · Ended 10:18 AM UTC

Commit: 0580eca · View workflow run →

@sonarqubecloud

Copy link
Copy Markdown

this.logger.debug(`Fetching open issues count for project ${projectKey}`);

// Pre-flight: /api/issues/search returns 200 with total: 0 for inaccessible
// projects, so verify the component exists and is reachable first.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

The pre-flight check adds an extra HTTP round-trip per getOpenIssuesCount call. The sequential approach is the correct trade-off for scorecard metric polling.

sonarqube: {
baseUrl: 'https://sonarcloud.io',
apiKey: 'test-key',
const config = mockServices.rootConfig({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] naming-conventions

Migrating from ConfigReader to mockServices.rootConfig in this file creates a minor inconsistency with sibling test files in the same plugin that still use ConfigReader.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Aug 14, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:00 AM UTC · Completed 10:18 AM UTC

Commit: 0580eca · View workflow run →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug fix ready-for-merge All reviewers approved — ready to merge Tests workspace/scorecard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant