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  }