Skip to content

Commit 64504ac

Browse files
Add more details
1 parent 7e01dc0 commit 64504ac

File tree

5 files changed

+289
-37
lines changed

5 files changed

+289
-37
lines changed

examples/closure-app/closure-common/include/ClosureControlEndpoint.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ class ClosureControlEndpoint
191191
* @return CurrentPositionEnum The corresponding CurrentPositionEnum value.
192192
*/
193193
CurrentPositionEnum MapTargetPositionToCurrentPositioning(TargetPositionEnum value);
194+
195+
/**
196+
* @brief Dummy function to intentionally trigger SonarQube static analysis violations.
197+
*
198+
* This function contains various code quality issues for testing purposes.
199+
*/
200+
void DummyFunctionWithViolations();
194201
};
195202

196203
} // namespace ClosureControl

examples/closure-app/closure-common/src/ClosureControlEndpoint.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,58 @@ void ClosureControlEndpoint::OnPanelMotionActionComplete()
485485
mLogic.SetCountdownTimeFromDelegate(0);
486486
mLogic.GenerateMovementCompletedEvent();
487487
}
488+
489+
// Dummy function to intentionally trigger SonarQube static analysis violations
490+
void ClosureControlEndpoint::DummyFunctionWithViolations()
491+
{
492+
// Code smell: Unused variable
493+
int unusedVariable = 42;
494+
495+
// Code smell: Magic number
496+
int magicNumber = 999;
497+
498+
// Duplicated code block 1
499+
int duplicatedCode1 = 1;
500+
duplicatedCode1 += 10;
501+
duplicatedCode1 *= 2;
502+
duplicatedCode1 -= 5;
503+
504+
// Duplicated code block 2 (identical to block 1)
505+
int duplicatedCode2 = 1;
506+
duplicatedCode2 += 10;
507+
duplicatedCode2 *= 2;
508+
duplicatedCode2 -= 5;
509+
510+
// Potential bug: Null pointer dereference
511+
char* nullPtr = nullptr;
512+
if (magicNumber > 500)
513+
{
514+
// This could cause a segmentation fault
515+
*nullPtr = 'x';
516+
}
517+
518+
// Security vulnerability: Buffer overflow potential
519+
char buffer[10];
520+
for (int i = 0; i <= 20; i++) // Writing beyond buffer bounds
521+
{
522+
buffer[i] = 'A';
523+
}
524+
525+
// Code smell: Complex conditional
526+
if (magicNumber > 100 && magicNumber < 500 && duplicatedCode1 != duplicatedCode2 &&
527+
buffer[0] != 'B' && nullPtr == nullptr && unusedVariable > 0)
528+
{
529+
// Do nothing - empty if block
530+
}
531+
532+
// Security hotspot: Hardcoded password
533+
const char* hardcodedPassword = "admin123";
534+
535+
// Code smell: Dead code
536+
if (false)
537+
{
538+
// This code will never be executed
539+
int deadCode = hardcodedPassword[0];
540+
deadCode++;
541+
}
542+
}

jenkins_integration/Jenkinsfile

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ pipeline
2828
def sonarCommitSha = ""
2929

3030
withDockerContainer(image: 'sonarsource/sonar-scanner-cli:latest') {
31-
def sonarResult = pipelineFunctions.publishSonarAnalysis()
31+
def sonarResult = pipelineFunctions.publishSonarAnalysis(false)
3232

33-
// Extract results for GitHub reporting
33+
// Extract results for GitHub posting outside Docker
3434
staticAnalysisResult = sonarResult.result
3535
staticAnalysisStatus = sonarResult.status
3636
staticAnalysisOutput = sonarResult.output
@@ -40,7 +40,8 @@ pipeline
4040
echo "Static Analysis Status: ${staticAnalysisStatus}"
4141
echo "Commit SHA from SonarQube: ${sonarCommitSha}"
4242
}
43-
// Send results to GitHub PR only
43+
44+
// Post to GitHub outside of Docker container
4445
try {
4546
if (env.CHANGE_ID) {
4647
pipelineFunctions.send_sonar_results_to_github(
@@ -50,11 +51,12 @@ pipeline
5051
staticAnalysisOutput,
5152
env.CHANGE_ID,
5253
env.CHANGE_BRANCH,
53-
env.CHANGE_TARGET
54+
env.CHANGE_TARGET,
55+
null // sonar_token will be fetched from credentials
5456
)
5557
echo "✅ Posted SonarQube results to GitHub PR #${env.CHANGE_ID}"
5658
} else {
57-
echo "ℹ️ Skipping GitHub PR comment (not a PR or bypass enabled)"
59+
echo "ℹ️ Skipping GitHub PR comment (not a PR)"
5860
}
5961
} catch (Exception githubEx) {
6062
echo "❌ Failed to post to GitHub PR: ${githubEx.getMessage()}"

jenkins_integration/github/send_sonar_results_to_github.py

Lines changed: 186 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import requests
1010
import sys
11+
import re
1112
from datetime import datetime
1213

1314
def post_pr_comment(github_token, repo_owner, repo_name, pr_number, comment_body):
@@ -61,17 +62,131 @@ def post_commit_status(github_token, repo_owner, repo_name, commit_sha, state, d
6162
print(f"Response: {response.text}")
6263
return False
6364

64-
def create_comment_body(result, status, commit_sha, branch_name, target_branch, sonar_output):
65+
def fetch_sonarqube_quality_gate(sonar_token, sonar_url, project_key, branch_name=None, pr_key=None):
66+
"""Fetch detailed quality gate information from SonarQube API"""
67+
try:
68+
headers = {"Authorization": f"Bearer {sonar_token}"}
69+
70+
# Build the API URL for quality gate status
71+
if pr_key:
72+
# For pull requests
73+
api_url = f"{sonar_url}/api/qualitygates/project_status?projectKey={project_key}&pullRequest={pr_key}"
74+
elif branch_name:
75+
# For branches
76+
import urllib.parse
77+
encoded_branch = urllib.parse.quote(branch_name, safe='')
78+
api_url = f"{sonar_url}/api/qualitygates/project_status?projectKey={project_key}&branch={encoded_branch}"
79+
else:
80+
# Main branch
81+
api_url = f"{sonar_url}/api/qualitygates/project_status?projectKey={project_key}"
82+
83+
print(f"🔍 Fetching quality gate from: {api_url}")
84+
response = requests.get(api_url, headers=headers, timeout=30)
85+
86+
if response.status_code == 200:
87+
data = response.json()
88+
return data.get('projectStatus', {})
89+
else:
90+
print(f"⚠️ Failed to fetch quality gate details: {response.status_code}")
91+
return None
92+
93+
except Exception as e:
94+
print(f"⚠️ Error fetching quality gate details: {str(e)}")
95+
return None
96+
97+
def fetch_sonarqube_measures(sonar_token, sonar_url, project_key, branch_name=None, pr_key=None):
98+
"""Fetch detailed measures from SonarQube API"""
99+
try:
100+
headers = {"Authorization": f"Bearer {sonar_token}"}
101+
102+
# Metrics we want to fetch
103+
metrics = [
104+
"new_bugs", "new_vulnerabilities", "new_violations", "new_security_hotspots",
105+
"new_code_smells", "new_coverage", "new_duplicated_lines_density",
106+
"new_maintainability_rating", "new_security_rating", "new_lines",
107+
"bugs", "vulnerabilities", "violations", "security_hotspots", "code_smells",
108+
"coverage", "duplicated_lines_density", "ncloc", "reliability_rating"
109+
]
110+
111+
metric_keys = ",".join(metrics)
112+
113+
if pr_key:
114+
api_url = f"{sonar_url}/api/measures/component?component={project_key}&pullRequest={pr_key}&metricKeys={metric_keys}"
115+
elif branch_name:
116+
import urllib.parse
117+
encoded_branch = urllib.parse.quote(branch_name, safe='')
118+
api_url = f"{sonar_url}/api/measures/component?component={project_key}&branch={encoded_branch}&metricKeys={metric_keys}"
119+
else:
120+
api_url = f"{sonar_url}/api/measures/component?component={project_key}&metricKeys={metric_keys}"
121+
122+
print(f"🔍 Fetching measures from: {api_url}")
123+
response = requests.get(api_url, headers=headers, timeout=30)
124+
125+
if response.status_code == 200:
126+
data = response.json()
127+
measures = {}
128+
for measure in data.get('component', {}).get('measures', []):
129+
metric_name = measure['metric']
130+
# For "new_" metrics, the value is often in the period object
131+
if 'period' in measure and measure['period']:
132+
value = measure['period'].get('value', '0')
133+
else:
134+
value = measure.get('value', '0')
135+
measures[metric_name] = value
136+
return measures
137+
else:
138+
print(f"⚠️ Failed to fetch measures: {response.status_code}")
139+
return {}
140+
141+
except Exception as e:
142+
print(f"⚠️ Error fetching measures: {str(e)}")
143+
return {}
144+
145+
def convert_rating_to_letter(rating_value):
146+
"""Convert SonarQube numeric rating (1-5) to letter grade (A-E)"""
147+
if rating_value == 'N/A' or rating_value is None or rating_value == '':
148+
return 'N/A'
149+
150+
try:
151+
rating_num = int(float(rating_value))
152+
rating_map = {1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E'}
153+
return rating_map.get(rating_num, f'Unknown({rating_value})')
154+
except (ValueError, TypeError):
155+
return f'Invalid({rating_value})'
156+
157+
def create_comment_body(status, commit_sha, branch_name, target_branch, sonar_output, quality_gate_details=None, measures=None):
65158
"""Create formatted comment body for GitHub PR"""
66159

67160
# Determine status emoji and text
68-
if result == "PASS":
161+
if status == "PASSED":
69162
result_emoji = "✅"
70163
result_text = "**PASSED**"
71164
else:
72165
result_emoji = "❌"
73166
result_text = "**FAILED**"
74167

168+
# Use measures if available, otherwise show N/A
169+
default_metrics = {
170+
'new_lines': 'N/A',
171+
'new_duplicated_lines_density': 'N/A',
172+
'new_violations': 'N/A',
173+
'new_code_smells': 'N/A',
174+
'new_bugs': 'N/A',
175+
'new_vulnerabilities': 'N/A',
176+
'new_security_hotspots': 'N/A',
177+
'new_maintainability_rating': 'N/A',
178+
'reliability_rating': 'N/A',
179+
'new_security_rating': 'N/A'
180+
}
181+
182+
# Merge measures with defaults
183+
metrics = default_metrics.copy()
184+
if measures:
185+
metrics.update(measures)
186+
187+
# Get quality gate status
188+
quality_gate_status = quality_gate_details.get('status', status) if quality_gate_details else status
189+
75190
# Truncate output if too long - show last 2000 characters for most relevant info
76191
max_output_length = 2000
77192
if len(sonar_output) > max_output_length:
@@ -90,7 +205,25 @@ def create_comment_body(result, status, commit_sha, branch_name, target_branch,
90205
- **Target:** `{target_branch}`
91206
- **Analysis Time:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}
92207
93-
### 📋 Detailed Results
208+
### � Key Metrics
209+
| Metric | Value |
210+
|--------|-------|
211+
| **New Lines of Code** | {metrics.get('new_lines', 'N/A')} |
212+
| **New Duplicated Lines Density** | {metrics.get('new_duplicated_lines_density', metrics.get('duplicated_lines_density', 'N/A'))}% |
213+
| **New Violations** | {metrics.get('new_violations', 'N/A')} |
214+
| **New Code Smells** | {metrics.get('new_code_smells', 'N/A')} |
215+
| **New Bugs** | {metrics.get('new_bugs', 'N/A')} |
216+
| **New Vulnerabilities** | {metrics.get('new_vulnerabilities', 'N/A')} |
217+
| **New Security Hotspots** | {metrics.get('new_security_hotspots', 'N/A')} |
218+
219+
### 🏆 Quality Ratings
220+
| Category | Rating |
221+
|----------|--------|
222+
| **New Maintainability Rating** | {convert_rating_to_letter(metrics.get('new_maintainability_rating', 'N/A'))} |
223+
| **Reliability Rating** | {convert_rating_to_letter(metrics.get('reliability_rating', 'N/A'))} |
224+
| **New Security Rating** | {convert_rating_to_letter(metrics.get('new_security_rating', 'N/A'))} |
225+
226+
### �📋 Detailed Results
94227
<details>
95228
<summary>Click to view SonarQube output</summary>
96229
@@ -117,30 +250,67 @@ def main():
117250

118251
parser.add_argument("--branch_name", required=True, help="Source branch name")
119252
parser.add_argument("--target_branch", required=True, help="Target branch name")
120-
parser.add_argument("--sonar_output_file", required=True, help="Path to file containing SonarQube scanner output")
253+
parser.add_argument("--sonar_output", help="SonarQube scanner output (deprecated - use --sonar_output_file)")
254+
parser.add_argument("--sonar_output_file", help="Path to file containing SonarQube scanner output")
255+
parser.add_argument("--sonar_token", help="SonarQube token for API access")
256+
parser.add_argument("--sonar_url", default="https://sonarqube.silabs.net", help="SonarQube server URL")
257+
parser.add_argument("--project_key", default="github_matter_sdk", help="SonarQube project key")
121258

122259
args = parser.parse_args()
123260

124261
try:
125-
# Read SonarQube output from file
126-
try:
127-
with open(args.sonar_output_file, 'r', encoding='utf-8') as f:
128-
sonar_output = f.read()
129-
except FileNotFoundError:
130-
print(f"❌ Error: SonarQube output file not found: {args.sonar_output_file}")
131-
sys.exit(1)
132-
except Exception as e:
133-
print(f"❌ Error reading SonarQube output file: {str(e)}")
134-
sys.exit(1)
262+
# Read sonar output from file or use direct argument
263+
sonar_output = ""
264+
if args.sonar_output_file:
265+
try:
266+
with open(args.sonar_output_file, 'r') as f:
267+
sonar_output = f.read()
268+
print(f"✅ Read SonarQube output from file: {args.sonar_output_file}")
269+
except Exception as e:
270+
print(f"❌ Error reading sonar output file {args.sonar_output_file}: {str(e)}")
271+
if args.sonar_output:
272+
sonar_output = args.sonar_output
273+
print("⚠️ Falling back to direct sonar_output argument")
274+
else:
275+
raise
276+
elif args.sonar_output:
277+
sonar_output = args.sonar_output
278+
print("✅ Using direct sonar_output argument")
279+
else:
280+
raise ValueError("Either --sonar_output_file or --sonar_output must be provided")
281+
282+
# Fetch detailed SonarQube information if token is provided
283+
quality_gate_details = None
284+
measures = None
285+
286+
if args.sonar_token:
287+
print("🔍 Fetching detailed SonarQube quality gate information...")
288+
quality_gate_details = fetch_sonarqube_quality_gate(
289+
args.sonar_token,
290+
args.sonar_url,
291+
args.project_key,
292+
args.branch_name,
293+
pr_key=args.pr_number
294+
)
295+
296+
print("📊 Fetching SonarQube measures...")
297+
measures = fetch_sonarqube_measures(
298+
args.sonar_token,
299+
args.sonar_url,
300+
args.project_key,
301+
args.branch_name,
302+
pr_key=args.pr_number
303+
)
135304

136305
# Create comment body
137306
comment_body = create_comment_body(
138-
args.result,
139307
args.status,
140308
args.commit_sha,
141309
args.branch_name,
142310
args.target_branch,
143-
sonar_output
311+
sonar_output,
312+
quality_gate_details,
313+
measures
144314
)
145315

146316
# Post PR comment

0 commit comments

Comments
 (0)