88import os
99import requests
1010import sys
11+ import re
1112from datetime import datetime
1213
1314def post_pr_comment (github_token , repo_owner , repo_name , pr_number , comment_body ):
@@ -61,7 +62,100 @@ 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_sonar_metrics (sonar_host , sonar_token , project_key , pr_number = None ):
66+ """Fetch SonarQube metrics from SonarQube server API"""
67+ metrics = {
68+ 'duplicated_lines' : 'N/A' ,
69+ 'new_violations' : 'N/A' ,
70+ 'new_code_smells' : 'N/A' ,
71+ 'new_maintainability_rating' : 'N/A' ,
72+ 'quality_gate_details' : 'N/A' ,
73+ 'new_bugs' : 'N/A' ,
74+ 'reliability_rating' : 'N/A' ,
75+ 'new_vulnerabilities' : 'N/A' ,
76+ 'new_security_rating' : 'N/A' ,
77+ 'new_security_hotspots' : 'N/A' ,
78+ 'ncloc' : 'N/A'
79+ }
80+
81+ try :
82+ # Prepare headers for SonarQube API
83+ headers = {
84+ 'Authorization' : f'Bearer { sonar_token } ' ,
85+ 'Accept' : 'application/json'
86+ }
87+
88+ # Determine component key - use pullRequest key if it's a PR analysis
89+ component_key = project_key
90+ if pr_number :
91+ component_key = f"{ project_key } /pull/{ pr_number } "
92+
93+ # Define metrics to fetch from SonarQube API
94+ metric_keys = [
95+ 'duplicated_lines' ,
96+ 'new_violations' ,
97+ 'new_code_smells' ,
98+ 'new_maintainability_rating' ,
99+ 'new_bugs' ,
100+ 'reliability_rating' ,
101+ 'new_vulnerabilities' ,
102+ 'new_security_rating' ,
103+ 'new_security_hotspots' ,
104+ 'ncloc'
105+ ]
106+
107+ # Fetch metrics from SonarQube API
108+ metrics_url = f"{ sonar_host } /api/measures/component"
109+ params = {
110+ 'component' : component_key ,
111+ 'metricKeys' : ',' .join (metric_keys )
112+ }
113+
114+ print (f"🔍 Fetching metrics from SonarQube: { metrics_url } " )
115+ print (f"📊 Component: { component_key } " )
116+
117+ response = requests .get (metrics_url , headers = headers , params = params , timeout = 30 )
118+
119+ if response .status_code == 200 :
120+ data = response .json ()
121+
122+ if 'component' in data and 'measures' in data ['component' ]:
123+ for measure in data ['component' ]['measures' ]:
124+ metric_key = measure .get ('metric' )
125+ value = measure .get ('value' , 'N/A' )
126+
127+ if metric_key in metrics :
128+ metrics [metric_key ] = value
129+ print (f"📈 { metric_key } : { value } " )
130+ else :
131+ print ("⚠️ No measures found in SonarQube response" )
132+ else :
133+ print (f"❌ Failed to fetch metrics from SonarQube: { response .status_code } " )
134+ print (f"Response: { response .text } " )
135+
136+ # Fetch quality gate status separately
137+ try :
138+ qg_url = f"{ sonar_host } /api/qualitygates/project_status"
139+ qg_params = {'projectKey' : component_key }
140+
141+ qg_response = requests .get (qg_url , headers = headers , params = qg_params , timeout = 30 )
142+
143+ if qg_response .status_code == 200 :
144+ qg_data = qg_response .json ()
145+ if 'projectStatus' in qg_data :
146+ metrics ['quality_gate_details' ] = qg_data ['projectStatus' ].get ('status' , 'N/A' )
147+ print (f"🚦 Quality Gate: { metrics ['quality_gate_details' ]} " )
148+ else :
149+ print (f"⚠️ Could not fetch quality gate status: { qg_response .status_code } " )
150+ except Exception as qg_ex :
151+ print (f"⚠️ Error fetching quality gate: { str (qg_ex )} " )
152+
153+ except Exception as e :
154+ print (f"❌ Error fetching SonarQube metrics: { str (e )} " )
155+
156+ return metrics
157+
158+ def create_comment_body (result , status , commit_sha , branch_name , target_branch , sonar_output , metrics ):
65159 """Create formatted comment body for GitHub PR"""
66160
67161 # Determine status emoji and text
@@ -90,7 +184,26 @@ def create_comment_body(result, status, commit_sha, branch_name, target_branch,
90184- **Target:** `{ target_branch } `
91185- **Analysis Time:** { datetime .now ().strftime ('%Y-%m-%d %H:%M:%S UTC' )}
92186
93- ### 📋 Detailed Results
187+ ### � Key Metrics
188+ | Metric | Value |
189+ |--------|-------|
190+ | **Lines of Code (NCLOC)** | { metrics ['ncloc' ]} |
191+ | **Duplicated Lines** | { metrics ['duplicated_lines' ]} |
192+ | **New Violations** | { metrics ['new_violations' ]} |
193+ | **New Code Smells** | { metrics ['new_code_smells' ]} |
194+ | **New Bugs** | { metrics ['new_bugs' ]} |
195+ | **New Vulnerabilities** | { metrics ['new_vulnerabilities' ]} |
196+ | **New Security Hotspots** | { metrics ['new_security_hotspots' ]} |
197+
198+ ### 🏆 Quality Ratings
199+ | Category | Rating |
200+ |----------|--------|
201+ | **New Maintainability Rating** | { metrics ['new_maintainability_rating' ]} |
202+ | **Reliability Rating** | { metrics ['reliability_rating' ]} |
203+ | **New Security Rating** | { metrics ['new_security_rating' ]} |
204+ | **Quality Gate Details** | { metrics ['quality_gate_details' ]} |
205+
206+ ### �📋 Detailed Results
94207<details>
95208<summary>Click to view SonarQube output</summary>
96209
@@ -118,6 +231,9 @@ def main():
118231 parser .add_argument ("--branch_name" , required = True , help = "Source branch name" )
119232 parser .add_argument ("--target_branch" , required = True , help = "Target branch name" )
120233 parser .add_argument ("--sonar_output_file" , required = True , help = "Path to file containing SonarQube scanner output" )
234+ parser .add_argument ("--sonar_host" , required = True , help = "SonarQube server host URL" )
235+ parser .add_argument ("--sonar_token" , required = True , help = "SonarQube authentication token" )
236+ parser .add_argument ("--sonar_project_key" , default = "matter_sdk" , help = "SonarQube project key" )
121237
122238 args = parser .parse_args ()
123239
@@ -133,14 +249,23 @@ def main():
133249 print (f"❌ Error reading SonarQube output file: { str (e )} " )
134250 sys .exit (1 )
135251
252+ # Fetch SonarQube metrics from SonarQube server
253+ metrics = fetch_sonar_metrics (
254+ args .sonar_host ,
255+ args .sonar_token ,
256+ args .sonar_project_key ,
257+ args .pr_number
258+ )
259+
136260 # Create comment body
137261 comment_body = create_comment_body (
138262 args .result ,
139263 args .status ,
140264 args .commit_sha ,
141265 args .branch_name ,
142266 args .target_branch ,
143- sonar_output
267+ sonar_output ,
268+ metrics
144269 )
145270
146271 # Post PR comment
0 commit comments