fix: migrate project settings to SonarCloud correctly (#95)#143
Conversation
Project configuration settings (sonar.exclusions, sonar.coverage.exclusions, etc.) were silently not being applied during migration. Three root causes: 1. POST body format — settings API sent params as URL query string with null body and Content-Type: application/json. SonarCloud requires form-encoded POST body with Content-Type: application/x-www-form-urlencoded. The axios client default header prevented auto-detection, so explicit header needed. 2. Truthy check — dispatchSettingToApi used `if (setting.value)` which skipped settings with empty string values. Fixed to null/undefined check. 3. Silent failures — no summary logging made diagnosis impossible. Added per-setting names and ok/fail/skip summary counters. Also fixed the same truthy fallback in verify-settings.js comparison logic. All 4 pipeline versions (sq-9.9, sq-10.0, sq-10.4, sq-2025) updated. Regression tested: 3 settings migrated SQ 2026.2 → SC-staging, exact match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SummaryThis PR fixes silent failures in project settings migration by addressing three interconnected issues: the Settings API POST format (query params + JSON Content-Type wasn't being parsed by SonarCloud), a JavaScript truthy check that skipped empty string values, and missing diagnostic logging. All changes are applied consistently across all 4 pipeline versions (sq-9.9, sq-10.0, sq-10.4, sq-2025). The key insight: SonarQube/SonarCloud web APIs expect form-encoded POST bodies, not query params. The axios default What reviewers should knowStart here: Then: Any one of the 4 Logging improvements: The Verification fix: Test coverage:
WalkthroughStart here: This is the only shared file touched and contains both core fixes — the truthy→null-check correction in File changes
Things worth extra attention
DiagramsequenceDiagram
participant migrate as migrateProjectSettings
participant dispatch as dispatchSettingToApi
participant apiHelper as setProjectSetting
participant SC as SonarCloud API
migrate->>dispatch: setting {key, value/values/fieldValues}
alt BEFORE: params as query string
apiHelper->>SC: POST /api/settings/set?key=…&value=…<br/>Body: null, Content-Type: application/json
SC-->>apiHelper: 200 OK (params silently ignored)
else AFTER: params as form body
apiHelper->>SC: POST /api/settings/set<br/>Body: key=…&value=…<br/>Content-Type: application/x-www-form-urlencoded
SC-->>apiHelper: 200 OK (params applied ✓)
end
dispatch-->>migrate: true / false
migrate->>migrate: log "N applied, N failed, N skipped"
%%{init: {'flowchart': {'curve': 'catmullRom'}}}%%
flowchart TD
start([dispatchSettingToApi]) --> checkValue{"value !== null\n&& !== undefined?\n(was: if value)"}
checkValue -- Yes\nincludes empty string --> sendScalar["setProjectSetting\n{ value }"]
checkValue -- No --> checkValues{"values[]\nnon-empty?"}
checkValues -- Yes --> sendValues["setProjectSetting\n{ values }"]
checkValues -- No --> checkFieldValues{"fieldValues[]\nnon-empty?"}
checkFieldValues -- Yes --> sendFieldValues["setProjectSetting\n{ fieldValues }"]
checkFieldValues -- No --> skip["log debug skip\nreturn false ❌"]
sendScalar & sendValues & sendFieldValues --> done["return true ✓"]
|
| @@ -13,13 +13,15 @@ export async function verifyProjectSettings(sqClient, scClient, sqProjectKey, sc | |||
| const sqNonInherited = sqSettings.filter(s => !s.inherited); | |||
| const scMap = new Map(scSettings.map(s => [s.key, s])); | |||
|
|
|||
There was a problem hiding this comment.
Logic duplication: pick reimplements the value-selection logic from dispatchSettingToApi in src/shared/utils/settings-params.js, but with a silent divergence: it omits fieldValues. dispatchSettingToApi handles all three shapes (value → values → fieldValues); pick only handles two.
Consequence: a setting that exists as fieldValues on both sides will have pick() return undefined for both, produce '""' on both sides, and always be reported as matching — even if the SC values are wrong. The only failure mode caught is if the setting is absent from SC entirely.
Extract a shared extractSettingValue(s) helper from settings-params.js and use it here:
// in settings-params.js (exported)
export function extractSettingValue(s) {
if (s.value !== undefined && s.value !== null) return s.value;
if (s.values?.length) return s.values;
if (s.fieldValues?.length) return s.fieldValues;
return undefined;
}Then verify-settings.js simply calls extractSettingValue(s) instead of the inline pick.
- Mark as noise
Summary
Fixes #95 — project configuration settings (e.g.,
sonar.exclusions,sonar.coverage.exclusions) were silently not being applied during migration from SonarQube Server to SonarQube Cloud.Three root causes identified and fixed:
nullbody and defaultContent-Type: application/json. SonarCloud's servlet framework won't parse form-encoded body when Content-Type says JSON. Fixed by sendingparams.toString()as POST body with explicitContent-Type: application/x-www-form-urlencodedheader.dispatchSettingToApiusedif (setting.value)which silently skipped settings with empty string values (""). Fixed to proper null/undefined check.Also fixed the same truthy fallback in
verify-settings.jscomparison logic. All 4 pipeline versions (sq-9.9, sq-10.0, sq-10.4, sq-2025) updated consistently.Test plan
settings-params.test.js+ 1 new + 11 existing inmigrators.test.js)mainsonar.exclusions3 values,sonar.coverage.exclusions2 values,sonar.cpd.exclusions1 value)3 applied, 0 failed, 0 skipped🤖 Generated with Claude Code
Note
Medium Risk
Changes how project settings are written to SonarCloud (request encoding, value dispatch rules, and migration logging), which can affect migration outcomes across all supported pipelines. While scoped to project settings, incorrect encoding could cause settings to fail to apply or apply differently in production.
Overview
Fixes project settings migration to SonarCloud by switching
/api/settings/setcalls to send a form-encoded POST body with an explicitContent-Type: application/x-www-form-urlencodedheader (instead of querystring params + null JSON body) across all pipeline versions.Updates
dispatchSettingToApiand project-settings verification to treat empty-string setting values as valid (null/undefined checks instead of truthy checks), adds skip/apply/fail counters and clearer logs during migration, and introduces targeted unit tests covering parameter building and empty-string dispatch behavior.Reviewed by Cursor Bugbot for commit 40f3a5b. Bugbot is set up for automated code reviews on this repo. Configure here.