2021-12-08 10:02:12 +02:00
package sonar
import (
"net/http"
"strconv"
2022-02-04 10:52:29 +02:00
"strings"
2021-12-08 10:02:12 +02:00
"github.com/SAP/jenkins-library/pkg/log"
sonargo "github.com/magicsong/sonargo/sonar"
"github.com/pkg/errors"
)
// EndpointIssuesSearch API endpoint for https://sonarcloud.io/web_api/api/measures/component
const EndpointMeasuresComponent = "measures/component"
// ComponentService ...
type ComponentService struct {
Organization string
Project string
2022-02-04 10:52:29 +02:00
Branch string
PullRequest string
2021-12-08 10:02:12 +02:00
apiClient * Requester
}
type SonarCoverage struct {
2022-02-04 10:52:29 +02:00
Coverage float32 ` json:"coverage" `
LineCoverage float32 ` json:"lineCoverage" `
LinesToCover int ` json:"linesToCover" `
UncoveredLines int ` json:"uncoveredLines" `
BranchCoverage float32 ` json:"branchCoverage" `
BranchesToCover int ` json:"branchesToCover" `
UncoveredBranches int ` json:"uncoveredBranches" `
2021-12-08 10:02:12 +02:00
}
2022-02-04 10:52:29 +02:00
type SonarLinesOfCode struct {
Total int ` json:"total" `
LanguageDistribution [ ] SonarLanguageDistribution ` json:"languageDistribution,omitempty" `
}
type SonarLanguageDistribution struct {
LanguageKey string ` json:"languageKey,omitempty" ` // Description:"key of the language as retrieved from sonarqube. All languages (key + name) are available as API https://<sonarqube-instance>/api/languages/list ",ExampleValue:"java,js,web,go"
LinesOfCode int ` json:"linesOfCode" `
}
func ( service * ComponentService ) Component ( options * MeasuresComponentOption ) ( * sonargo . MeasuresComponentObject , * http . Response , error ) {
2022-11-07 14:10:47 +02:00
// if PR, ignore branch name and consider PR branch name. If not PR, consider branch name
2022-02-04 10:52:29 +02:00
if len ( service . PullRequest ) > 0 {
options . PullRequest = service . PullRequest
2022-11-07 14:10:47 +02:00
} else if len ( service . Branch ) > 0 {
options . Branch = service . Branch
2022-02-04 10:52:29 +02:00
}
2021-12-08 10:02:12 +02:00
request , err := service . apiClient . create ( "GET" , EndpointMeasuresComponent , options )
if err != nil {
return nil , nil , err
}
// use custom HTTP client to send request
response , err := service . apiClient . send ( request )
if err != nil {
return nil , nil , err
}
// reuse response verrification from sonargo
err = sonargo . CheckResponse ( response )
if err != nil {
return nil , response , err
}
// decode JSON response
result := new ( sonargo . MeasuresComponentObject )
err = service . apiClient . decode ( response , result )
if err != nil {
return nil , response , err
}
return result , response , nil
}
2022-02-04 10:52:29 +02:00
func ( service * ComponentService ) GetLinesOfCode ( ) ( * SonarLinesOfCode , error ) {
options := MeasuresComponentOption {
2021-12-08 10:02:12 +02:00
Component : service . Project ,
2022-02-04 10:52:29 +02:00
MetricKeys : "ncloc_language_distribution,ncloc" ,
2021-12-08 10:02:12 +02:00
}
2022-02-07 17:41:36 +02:00
component , response , err := service . Component ( & options )
if err != nil {
return nil , errors . Wrap ( err , "Failed to get coverage from Sonar measures/component API" )
}
2021-12-08 10:02:12 +02:00
// reuse response verification from sonargo
2022-02-07 17:41:36 +02:00
err = sonargo . CheckResponse ( response )
2021-12-08 10:02:12 +02:00
if err != nil {
2022-02-04 10:52:29 +02:00
return nil , errors . Wrap ( err , "Failed to get lines of code from Sonar measures/component API" )
2021-12-08 10:02:12 +02:00
}
measures := component . Component . Measures
2022-02-04 10:52:29 +02:00
loc := & SonarLinesOfCode { }
2021-12-08 10:02:12 +02:00
for _ , element := range measures {
2022-02-04 10:52:29 +02:00
var err error
switch element . Metric {
case "ncloc" :
loc . Total , err = parseMeasureValueInt ( * element )
case "ncloc_language_distribution" :
loc . LanguageDistribution , err = parseMeasureLanguageDistribution ( * element )
default :
log . Entry ( ) . Debugf ( "Received unhandled lines of code metric from Sonar measures/component API. (Metric: %s, Value: %s)" , element . Metric , element . Value )
}
2021-12-08 10:02:12 +02:00
if err != nil {
2022-02-04 10:52:29 +02:00
// there was an error in the type conversion
2021-12-08 10:02:12 +02:00
return nil , err
}
2022-02-04 10:52:29 +02:00
}
return loc , nil
}
func ( service * ComponentService ) GetCoverage ( ) ( * SonarCoverage , error ) {
options := MeasuresComponentOption {
Component : service . Project ,
MetricKeys : "coverage,branch_coverage,line_coverage,uncovered_lines,lines_to_cover,conditions_to_cover,uncovered_conditions" ,
}
2022-02-07 17:41:36 +02:00
component , response , err := service . Component ( & options )
if err != nil {
return nil , errors . Wrap ( err , "Failed to get coverage from Sonar measures/component API" )
}
2022-02-04 10:52:29 +02:00
// reuse response verification from sonargo
2022-02-07 17:41:36 +02:00
err = sonargo . CheckResponse ( response )
2022-02-04 10:52:29 +02:00
if err != nil {
return nil , errors . Wrap ( err , "Failed to get coverage from Sonar measures/component API" )
}
measures := component . Component . Measures
cov := & SonarCoverage { }
for _ , element := range measures {
var err error
2021-12-08 10:02:12 +02:00
switch element . Metric {
case "coverage" :
2022-02-04 10:52:29 +02:00
cov . Coverage , err = parseMeasureValuef32 ( * element )
2021-12-08 10:02:12 +02:00
case "branch_coverage" :
2022-02-04 10:52:29 +02:00
cov . BranchCoverage , err = parseMeasureValuef32 ( * element )
2021-12-08 10:02:12 +02:00
case "line_coverage" :
2022-02-04 10:52:29 +02:00
cov . LineCoverage , err = parseMeasureValuef32 ( * element )
case "uncovered_lines" :
cov . UncoveredLines , err = parseMeasureValueInt ( * element )
case "lines_to_cover" :
cov . LinesToCover , err = parseMeasureValueInt ( * element )
case "conditions_to_cover" :
cov . BranchesToCover , err = parseMeasureValueInt ( * element )
case "uncovered_conditions" :
cov . UncoveredBranches , err = parseMeasureValueInt ( * element )
2021-12-08 10:02:12 +02:00
default :
log . Entry ( ) . Debugf ( "Received unhandled coverage metric from Sonar measures/component API. (Metric: %s, Value: %s)" , element . Metric , element . Value )
}
2022-02-04 10:52:29 +02:00
if err != nil {
// there was an error in the type conversion
return nil , err
}
2021-12-08 10:02:12 +02:00
}
return cov , nil
}
// NewMeasuresComponentService returns a new instance of a service for the measures/component endpoint.
2022-02-04 10:52:29 +02:00
func NewMeasuresComponentService ( host , token , project , organization , branch , pullRequest string , client Sender ) * ComponentService {
2021-12-08 10:02:12 +02:00
return & ComponentService {
Organization : organization ,
Project : project ,
2022-02-04 10:52:29 +02:00
Branch : branch ,
PullRequest : pullRequest ,
2021-12-08 10:02:12 +02:00
apiClient : NewAPIClient ( host , token , client ) ,
}
}
func parseMeasureValuef32 ( measure sonargo . SonarMeasure ) ( float32 , error ) {
str := measure . Value
f64 , err := strconv . ParseFloat ( str , 32 )
if err != nil {
return 0.0 , errors . Wrap ( err , "Invalid value found in measure " + measure . Metric + ": " + measure . Value )
}
return float32 ( f64 ) , nil
}
2022-02-04 10:52:29 +02:00
func parseMeasureValueInt ( measure sonargo . SonarMeasure ) ( int , error ) {
str := measure . Value
val , err := strconv . Atoi ( str )
if err != nil {
return 0 , errors . Wrap ( err , "Invalid value found in measure " + measure . Metric + ": " + measure . Value )
}
return int ( val ) , nil
}
func parseMeasureLanguageDistribution ( measure sonargo . SonarMeasure ) ( [ ] SonarLanguageDistribution , error ) {
str := measure . Value // example: js=589;ts=16544;web=1377
var ld [ ] SonarLanguageDistribution
entries := strings . Split ( str , ";" )
for _ , entry := range entries {
dist := strings . Split ( entry , "=" )
if len ( dist ) != 2 {
return nil , errors . New ( "Not able to split value " + entry + " at '=' found in measure " + measure . Metric + ": " + measure . Value )
}
loc , err := strconv . Atoi ( dist [ 1 ] )
if err != nil {
return nil , errors . Wrap ( err , "Not able to parse value " + dist [ 1 ] + " found in measure " + measure . Metric + ": " + measure . Value )
}
ld = append ( ld , SonarLanguageDistribution { LanguageKey : dist [ 0 ] , LinesOfCode : loc } )
}
return ld , nil
}