github.com/jaylevin/jenkins-library@v1.230.4/pkg/sonar/componentService.go (about) 1 package sonar 2 3 import ( 4 "net/http" 5 "strconv" 6 "strings" 7 8 "github.com/SAP/jenkins-library/pkg/log" 9 sonargo "github.com/magicsong/sonargo/sonar" 10 "github.com/pkg/errors" 11 ) 12 13 // EndpointIssuesSearch API endpoint for https://sonarcloud.io/web_api/api/measures/component 14 const EndpointMeasuresComponent = "measures/component" 15 16 // ComponentService ... 17 type ComponentService struct { 18 Organization string 19 Project string 20 Branch string 21 PullRequest string 22 apiClient *Requester 23 } 24 25 type SonarCoverage struct { 26 Coverage float32 `json:"coverage"` 27 LineCoverage float32 `json:"lineCoverage"` 28 LinesToCover int `json:"linesToCover"` 29 UncoveredLines int `json:"uncoveredLines"` 30 BranchCoverage float32 `json:"branchCoverage"` 31 BranchesToCover int `json:"branchesToCover"` 32 UncoveredBranches int `json:"uncoveredBranches"` 33 } 34 35 type SonarLinesOfCode struct { 36 Total int `json:"total"` 37 LanguageDistribution []SonarLanguageDistribution `json:"languageDistribution,omitempty"` 38 } 39 40 type SonarLanguageDistribution struct { 41 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" 42 LinesOfCode int `json:"linesOfCode"` 43 } 44 45 func (service *ComponentService) Component(options *MeasuresComponentOption) (*sonargo.MeasuresComponentObject, *http.Response, error) { 46 if len(service.Branch) > 0 { 47 options.Branch = service.Branch 48 } 49 if len(service.PullRequest) > 0 { 50 options.PullRequest = service.PullRequest 51 } 52 request, err := service.apiClient.create("GET", EndpointMeasuresComponent, options) 53 if err != nil { 54 return nil, nil, err 55 } 56 // use custom HTTP client to send request 57 response, err := service.apiClient.send(request) 58 if err != nil { 59 return nil, nil, err 60 } 61 // reuse response verrification from sonargo 62 err = sonargo.CheckResponse(response) 63 if err != nil { 64 return nil, response, err 65 } 66 // decode JSON response 67 result := new(sonargo.MeasuresComponentObject) 68 err = service.apiClient.decode(response, result) 69 if err != nil { 70 return nil, response, err 71 } 72 return result, response, nil 73 } 74 75 func (service *ComponentService) GetLinesOfCode() (*SonarLinesOfCode, error) { 76 options := MeasuresComponentOption{ 77 Component: service.Project, 78 MetricKeys: "ncloc_language_distribution,ncloc", 79 } 80 component, response, err := service.Component(&options) 81 82 if err != nil { 83 return nil, errors.Wrap(err, "Failed to get coverage from Sonar measures/component API") 84 } 85 86 // reuse response verification from sonargo 87 err = sonargo.CheckResponse(response) 88 if err != nil { 89 return nil, errors.Wrap(err, "Failed to get lines of code from Sonar measures/component API") 90 } 91 measures := component.Component.Measures 92 93 loc := &SonarLinesOfCode{} 94 95 for _, element := range measures { 96 97 var err error 98 99 switch element.Metric { 100 case "ncloc": 101 loc.Total, err = parseMeasureValueInt(*element) 102 case "ncloc_language_distribution": 103 loc.LanguageDistribution, err = parseMeasureLanguageDistribution(*element) 104 default: 105 log.Entry().Debugf("Received unhandled lines of code metric from Sonar measures/component API. (Metric: %s, Value: %s)", element.Metric, element.Value) 106 } 107 if err != nil { 108 // there was an error in the type conversion 109 return nil, err 110 } 111 } 112 return loc, nil 113 } 114 115 func (service *ComponentService) GetCoverage() (*SonarCoverage, error) { 116 options := MeasuresComponentOption{ 117 Component: service.Project, 118 MetricKeys: "coverage,branch_coverage,line_coverage,uncovered_lines,lines_to_cover,conditions_to_cover,uncovered_conditions", 119 } 120 component, response, err := service.Component(&options) 121 if err != nil { 122 return nil, errors.Wrap(err, "Failed to get coverage from Sonar measures/component API") 123 } 124 125 // reuse response verification from sonargo 126 err = sonargo.CheckResponse(response) 127 if err != nil { 128 return nil, errors.Wrap(err, "Failed to get coverage from Sonar measures/component API") 129 } 130 measures := component.Component.Measures 131 132 cov := &SonarCoverage{} 133 134 for _, element := range measures { 135 136 var err error 137 138 switch element.Metric { 139 case "coverage": 140 cov.Coverage, err = parseMeasureValuef32(*element) 141 case "branch_coverage": 142 cov.BranchCoverage, err = parseMeasureValuef32(*element) 143 case "line_coverage": 144 cov.LineCoverage, err = parseMeasureValuef32(*element) 145 case "uncovered_lines": 146 cov.UncoveredLines, err = parseMeasureValueInt(*element) 147 case "lines_to_cover": 148 cov.LinesToCover, err = parseMeasureValueInt(*element) 149 case "conditions_to_cover": 150 cov.BranchesToCover, err = parseMeasureValueInt(*element) 151 case "uncovered_conditions": 152 cov.UncoveredBranches, err = parseMeasureValueInt(*element) 153 default: 154 log.Entry().Debugf("Received unhandled coverage metric from Sonar measures/component API. (Metric: %s, Value: %s)", element.Metric, element.Value) 155 } 156 if err != nil { 157 // there was an error in the type conversion 158 return nil, err 159 } 160 } 161 return cov, nil 162 } 163 164 // NewMeasuresComponentService returns a new instance of a service for the measures/component endpoint. 165 func NewMeasuresComponentService(host, token, project, organization, branch, pullRequest string, client Sender) *ComponentService { 166 return &ComponentService{ 167 Organization: organization, 168 Project: project, 169 Branch: branch, 170 PullRequest: pullRequest, 171 apiClient: NewAPIClient(host, token, client), 172 } 173 } 174 175 func parseMeasureValuef32(measure sonargo.SonarMeasure) (float32, error) { 176 str := measure.Value 177 f64, err := strconv.ParseFloat(str, 32) 178 if err != nil { 179 return 0.0, errors.Wrap(err, "Invalid value found in measure "+measure.Metric+": "+measure.Value) 180 } 181 return float32(f64), nil 182 } 183 184 func parseMeasureValueInt(measure sonargo.SonarMeasure) (int, error) { 185 str := measure.Value 186 val, err := strconv.Atoi(str) 187 if err != nil { 188 return 0, errors.Wrap(err, "Invalid value found in measure "+measure.Metric+": "+measure.Value) 189 } 190 return int(val), nil 191 } 192 193 func parseMeasureLanguageDistribution(measure sonargo.SonarMeasure) ([]SonarLanguageDistribution, error) { 194 str := measure.Value // example: js=589;ts=16544;web=1377 195 var ld []SonarLanguageDistribution 196 entries := strings.Split(str, ";") 197 198 for _, entry := range entries { 199 200 dist := strings.Split(entry, "=") 201 202 if len(dist) != 2 { 203 return nil, errors.New("Not able to split value " + entry + " at '=' found in measure " + measure.Metric + ": " + measure.Value) 204 } 205 206 loc, err := strconv.Atoi(dist[1]) 207 if err != nil { 208 return nil, errors.Wrap(err, "Not able to parse value "+dist[1]+" found in measure "+measure.Metric+": "+measure.Value) 209 } 210 ld = append(ld, SonarLanguageDistribution{LanguageKey: dist[0], LinesOfCode: loc}) 211 212 } 213 214 return ld, nil 215 }